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; 27 28 import sun.swing.JLightweightFrame; 29 30 import java.awt.*; 31 import java.awt.event.*; 32 import java.awt.peer.ComponentPeer; 33 import java.beans.Transient; 34 import javax.swing.plaf.ViewportUI; 35 36 import javax.swing.event.*; 37 import javax.swing.border.*; 38 import javax.accessibility.*; 39 40 41 import java.io.Serializable; 42 43 44 /** 45 * The "viewport" or "porthole" through which you see the underlying 46 * information. When you scroll, what moves is the viewport. It is like 47 * peering through a camera's viewfinder. Moving the viewfinder upwards 48 * brings new things into view at the top of the picture and loses 49 * things that were at the bottom. 50 * <p> 51 * By default, <code>JViewport</code> is opaque. To change this, use the 52 * <code>setOpaque</code> method. 53 * <p> 54 * <b>NOTE:</b>We have implemented a faster scrolling algorithm that 55 * does not require a buffer to draw in. The algorithm works as follows: 56 * <ol><li>The view and parent view and checked to see if they are 57 * <code>JComponents</code>, 58 * if they aren't, stop and repaint the whole viewport. 59 * <li>If the viewport is obscured by an ancestor, stop and repaint the whole 60 * viewport. 61 * <li>Compute the region that will become visible, if it is as big as 62 * the viewport, stop and repaint the whole view region. 63 * <li>Obtain the ancestor <code>Window</code>'s graphics and 64 * do a <code>copyArea</code> on the scrolled region. 65 * <li>Message the view to repaint the newly visible region. 66 * <li>The next time paint is invoked on the viewport, if the clip region 67 * is smaller than the viewport size a timer is kicked off to repaint the 68 * whole region. 69 * </ol> 70 * In general this approach is much faster. Compared to the backing store 71 * approach this avoids the overhead of maintaining an offscreen buffer and 72 * having to do two <code>copyArea</code>s. 73 * Compared to the non backing store case this 74 * approach will greatly reduce the painted region. 75 * <p> 76 * This approach can cause slower times than the backing store approach 77 * when the viewport is obscured by another window, or partially offscreen. 78 * When another window 79 * obscures the viewport the copyArea will copy garbage and a 80 * paint event will be generated by the system to inform us we need to 81 * paint the newly exposed region. The only way to handle this is to 82 * repaint the whole viewport, which can cause slower performance than the 83 * backing store case. In most applications very rarely will the user be 84 * scrolling while the viewport is obscured by another window or offscreen, 85 * so this optimization is usually worth the performance hit when obscured. 86 * <p> 87 * <strong>Warning:</strong> Swing is not thread safe. For more 88 * information see <a 89 * href="package-summary.html#threading">Swing's Threading 90 * Policy</a>. 91 * <p> 92 * <strong>Warning:</strong> 93 * Serialized objects of this class will not be compatible with 94 * future Swing releases. The current serialization support is 95 * appropriate for short term storage or RMI between applications running 96 * the same version of Swing. As of 1.4, support for long term storage 97 * of all JavaBeans™ 98 * has been added to the <code>java.beans</code> package. 99 * Please see {@link java.beans.XMLEncoder}. 100 * 101 * @author Hans Muller 102 * @author Philip Milne 103 * @see JScrollPane 104 */ 105 @SuppressWarnings("serial") // Same-version serialization only 106 public class JViewport extends JComponent implements Accessible 107 { 108 /** 109 * @see #getUIClassID 110 * @see #readObject 111 */ 112 private static final String uiClassID = "ViewportUI"; 113 114 /** Property used to indicate window blitting should not be done. 115 */ 116 static final Object EnableWindowBlit = "EnableWindowBlit"; 117 118 /** 119 * True when the viewport dimensions have been determined. 120 * The default is false. 121 */ 122 protected boolean isViewSizeSet = false; 123 124 /** 125 * The last <code>viewPosition</code> that we've painted, so we know how 126 * much of the backing store image is valid. 127 */ 128 protected Point lastPaintPosition = null; 129 130 /** 131 * True when this viewport is maintaining an offscreen image of its 132 * contents, so that some scrolling can take place using fast "bit-blit" 133 * operations instead of by accessing the view object to construct the 134 * display. The default is <code>false</code>. 135 * 136 * @deprecated As of Java 2 platform v1.3 137 * @see #setScrollMode 138 */ 139 @Deprecated 140 protected boolean backingStore = false; 141 142 /** The view image used for a backing store. */ 143 transient protected Image backingStoreImage = null; 144 145 /** 146 * The <code>scrollUnderway</code> flag is used for components like 147 * <code>JList</code>. When the downarrow key is pressed on a 148 * <code>JList</code> and the selected 149 * cell is the last in the list, the <code>scrollpane</code> autoscrolls. 150 * Here, the old selected cell needs repainting and so we need 151 * a flag to make the viewport do the optimized painting 152 * only when there is an explicit call to 153 * <code>setViewPosition(Point)</code>. 154 * When <code>setBounds</code> is called through other routes, 155 * the flag is off and the view repaints normally. Another approach 156 * would be to remove this from the <code>JViewport</code> 157 * class and have the <code>JList</code> manage this case by using 158 * <code>setBackingStoreEnabled</code>. The default is 159 * <code>false</code>. 160 */ 161 protected boolean scrollUnderway = false; 162 163 /* 164 * Listener that is notified each time the view changes size. 165 */ 166 private ComponentListener viewListener = null; 167 168 /* Only one <code>ChangeEvent</code> is needed per 169 * <code>JViewport</code> instance since the 170 * event's only (read-only) state is the source property. The source 171 * of events generated here is always "this". 172 */ 173 private transient ChangeEvent changeEvent = null; 174 175 /** 176 * Use <code>graphics.copyArea</code> to implement scrolling. 177 * This is the fastest for most applications. 178 * 179 * @see #setScrollMode 180 * @since 1.3 181 */ 182 public static final int BLIT_SCROLL_MODE = 1; 183 184 /** 185 * Draws viewport contents into an offscreen image. 186 * This was previously the default mode for <code>JTable</code>. 187 * This mode may offer advantages over "blit mode" 188 * in some cases, but it requires a large chunk of extra RAM. 189 * 190 * @see #setScrollMode 191 * @since 1.3 192 */ 193 public static final int BACKINGSTORE_SCROLL_MODE = 2; 194 195 /** 196 * This mode uses the very simple method of redrawing the entire 197 * contents of the scrollpane each time it is scrolled. 198 * This was the default behavior in Swing 1.0 and Swing 1.1. 199 * Either of the other two options will provide better performance 200 * in most cases. 201 * 202 * @see #setScrollMode 203 * @since 1.3 204 */ 205 public static final int SIMPLE_SCROLL_MODE = 0; 206 207 /** 208 * @see #setScrollMode 209 * @since 1.3 210 */ 211 private int scrollMode = BLIT_SCROLL_MODE; 212 213 // 214 // Window blitting: 215 // 216 // As mentioned in the javadoc when using windowBlit a paint event 217 // will be generated by the system if copyArea copies a non-visible 218 // portion of the view (in other words, it copies garbage). We are 219 // not guaranteed to receive the paint event before other mouse events, 220 // so we can not be sure we haven't already copied garbage a bunch of 221 // times to different parts of the view. For that reason when a blit 222 // happens and the Component is obscured (the check for obscurity 223 // is not supported on all platforms and is checked via ComponentPeer 224 // methods) the ivar repaintAll is set to true. When paint is received 225 // if repaintAll is true (we previously did a blit) it is set to 226 // false, and if the clip region is smaller than the viewport 227 // waitingForRepaint is set to true and a timer is started. When 228 // the timer fires if waitingForRepaint is true, repaint is invoked. 229 // In the mean time, if the view is asked to scroll and waitingForRepaint 230 // is true, a blit will not happen, instead the non-backing store case 231 // of scrolling will happen, which will reset waitingForRepaint. 232 // waitingForRepaint is set to false in paint when the clip rect is 233 // bigger (or equal) to the size of the viewport. 234 // A Timer is used instead of just a repaint as it appeared to offer 235 // better performance. 236 237 238 /** 239 * This is set to true in <code>setViewPosition</code> 240 * if doing a window blit and the viewport is obscured. 241 */ 242 private transient boolean repaintAll; 243 244 /** 245 * This is set to true in paint, if <code>repaintAll</code> 246 * is true and the clip rectangle does not match the bounds. 247 * If true, and scrolling happens the 248 * repaint manager is not cleared which then allows for the repaint 249 * previously invoked to succeed. 250 */ 251 private transient boolean waitingForRepaint; 252 253 /** 254 * Instead of directly invoking repaint, a <code>Timer</code> 255 * is started and when it fires, repaint is invoked. 256 */ 257 private transient Timer repaintTimer; 258 259 /** 260 * Set to true in paintView when paint is invoked. 261 */ 262 private transient boolean inBlitPaint; 263 264 /** 265 * Whether or not a valid view has been installed. 266 */ 267 private boolean hasHadValidView; 268 269 /** 270 * When view is changed we have to synchronize scrollbar values 271 * with viewport (see the BasicScrollPaneUI#syncScrollPaneWithViewport method). 272 * This flag allows to invoke that method while ScrollPaneLayout#layoutContainer 273 * is running. 274 */ 275 private boolean viewChanged; 276 277 /** Creates a <code>JViewport</code>. */ 278 public JViewport() { 279 super(); 280 setLayout(createLayoutManager()); 281 setOpaque(true); 282 updateUI(); 283 setInheritsPopupMenu(true); 284 } 285 286 287 288 /** 289 * Returns the L&F object that renders this component. 290 * 291 * @return a <code>ViewportUI</code> object 292 * @since 1.3 293 */ 294 public ViewportUI getUI() { 295 return (ViewportUI)ui; 296 } 297 298 299 /** 300 * Sets the L&F object that renders this component. 301 * 302 * @param ui the <code>ViewportUI</code> L&F object 303 * @see UIDefaults#getUI 304 * @beaninfo 305 * bound: true 306 * hidden: true 307 * attribute: visualUpdate true 308 * description: The UI object that implements the Component's LookAndFeel. 309 * @since 1.3 310 */ 311 public void setUI(ViewportUI ui) { 312 super.setUI(ui); 313 } 314 315 316 /** 317 * Resets the UI property to a value from the current look and feel. 318 * 319 * @see JComponent#updateUI 320 */ 321 public void updateUI() { 322 setUI((ViewportUI)UIManager.getUI(this)); 323 } 324 325 326 /** 327 * Returns a string that specifies the name of the L&F class 328 * that renders this component. 329 * 330 * @return the string "ViewportUI" 331 * 332 * @see JComponent#getUIClassID 333 * @see UIDefaults#getUI 334 */ 335 public String getUIClassID() { 336 return uiClassID; 337 } 338 339 340 /** 341 * Sets the <code>JViewport</code>'s one lightweight child, 342 * which can be <code>null</code>. 343 * (Since there is only one child which occupies the entire viewport, 344 * the <code>constraints</code> and <code>index</code> 345 * arguments are ignored.) 346 * 347 * @param child the lightweight <code>child</code> of the viewport 348 * @param constraints the <code>constraints</code> to be respected 349 * @param index the index 350 * @see #setView 351 */ 352 protected void addImpl(Component child, Object constraints, int index) { 353 setView(child); 354 } 355 356 357 /** 358 * Removes the <code>Viewport</code>s one lightweight child. 359 * 360 * @see #setView 361 */ 362 public void remove(Component child) { 363 child.removeComponentListener(viewListener); 364 super.remove(child); 365 } 366 367 @Override 368 public void addNotify() { 369 super.addNotify(); 370 // JLightweightFrame does not support BLIT_SCROLL_MODE, so it should be replaced 371 Window rootWindow = SwingUtilities.getWindowAncestor(this); 372 if (rootWindow instanceof JLightweightFrame 373 && getScrollMode() == BLIT_SCROLL_MODE) { 374 setScrollMode(BACKINGSTORE_SCROLL_MODE); 375 } 376 } 377 378 379 /** 380 * Scrolls the view so that <code>Rectangle</code> 381 * within the view becomes visible. 382 * <p> 383 * This attempts to validate the view before scrolling if the 384 * view is currently not valid - <code>isValid</code> returns false. 385 * To avoid excessive validation when the containment hierarchy is 386 * being created this will not validate if one of the ancestors does not 387 * have a peer, or there is no validate root ancestor, or one of the 388 * ancestors is not a <code>Window</code> or <code>Applet</code>. 389 * <p> 390 * Note that this method will not scroll outside of the 391 * valid viewport; for example, if <code>contentRect</code> is larger 392 * than the viewport, scrolling will be confined to the viewport's 393 * bounds. 394 * 395 * @param contentRect the <code>Rectangle</code> to display 396 * @see JComponent#isValidateRoot 397 * @see java.awt.Component#isValid 398 * @see java.awt.Component#getPeer 399 */ 400 public void scrollRectToVisible(Rectangle contentRect) { 401 Component view = getView(); 402 403 if (view == null) { 404 return; 405 } else { 406 if (!view.isValid()) { 407 // If the view is not valid, validate. scrollRectToVisible 408 // may fail if the view is not valid first, contentRect 409 // could be bigger than invalid size. 410 validateView(); 411 } 412 int dx, dy; 413 414 dx = positionAdjustment(getWidth(), contentRect.width, contentRect.x); 415 dy = positionAdjustment(getHeight(), contentRect.height, contentRect.y); 416 417 if (dx != 0 || dy != 0) { 418 Point viewPosition = getViewPosition(); 419 Dimension viewSize = view.getSize(); 420 int startX = viewPosition.x; 421 int startY = viewPosition.y; 422 Dimension extent = getExtentSize(); 423 424 viewPosition.x -= dx; 425 viewPosition.y -= dy; 426 // Only constrain the location if the view is valid. If the 427 // the view isn't valid, it typically indicates the view 428 // isn't visible yet and most likely has a bogus size as will 429 // we, and therefore we shouldn't constrain the scrolling 430 if (view.isValid()) { 431 if (getParent().getComponentOrientation().isLeftToRight()) { 432 if (viewPosition.x + extent.width > viewSize.width) { 433 viewPosition.x = Math.max(0, viewSize.width - extent.width); 434 } else if (viewPosition.x < 0) { 435 viewPosition.x = 0; 436 } 437 } else { 438 if (extent.width > viewSize.width) { 439 viewPosition.x = viewSize.width - extent.width; 440 } else { 441 viewPosition.x = Math.max(0, Math.min(viewSize.width - extent.width, viewPosition.x)); 442 } 443 } 444 if (viewPosition.y + extent.height > viewSize.height) { 445 viewPosition.y = Math.max(0, viewSize.height - 446 extent.height); 447 } 448 else if (viewPosition.y < 0) { 449 viewPosition.y = 0; 450 } 451 } 452 if (viewPosition.x != startX || viewPosition.y != startY) { 453 setViewPosition(viewPosition); 454 // NOTE: How JViewport currently works with the 455 // backing store is not foolproof. The sequence of 456 // events when setViewPosition 457 // (scrollRectToVisible) is called is to reset the 458 // views bounds, which causes a repaint on the 459 // visible region and sets an ivar indicating 460 // scrolling (scrollUnderway). When 461 // JViewport.paint is invoked if scrollUnderway is 462 // true, the backing store is blitted. This fails 463 // if between the time setViewPosition is invoked 464 // and paint is received another repaint is queued 465 // indicating part of the view is invalid. There 466 // is no way for JViewport to notice another 467 // repaint has occurred and it ends up blitting 468 // what is now a dirty region and the repaint is 469 // never delivered. 470 // It just so happens JTable encounters this 471 // behavior by way of scrollRectToVisible, for 472 // this reason scrollUnderway is set to false 473 // here, which effectively disables the backing 474 // store. 475 scrollUnderway = false; 476 } 477 } 478 } 479 } 480 481 /** 482 * Ascends the <code>Viewport</code>'s parents stopping when 483 * a component is found that returns 484 * <code>true</code> to <code>isValidateRoot</code>. 485 * If all the <code>Component</code>'s parents are visible, 486 * <code>validate</code> will then be invoked on it. The 487 * <code>RepaintManager</code> is then invoked with 488 * <code>removeInvalidComponent</code>. This 489 * is the synchronous version of a <code>revalidate</code>. 490 */ 491 private void validateView() { 492 Component validateRoot = SwingUtilities.getValidateRoot(this, false); 493 494 if (validateRoot == null) { 495 return; 496 } 497 498 // Validate the root. 499 validateRoot.validate(); 500 501 // And let the RepaintManager it does not have to validate from 502 // validateRoot anymore. 503 RepaintManager rm = RepaintManager.currentManager(this); 504 505 if (rm != null) { 506 rm.removeInvalidComponent((JComponent)validateRoot); 507 } 508 } 509 510 /* Used by the scrollRectToVisible method to determine the 511 * proper direction and amount to move by. The integer variables are named 512 * width, but this method is applicable to height also. The code assumes that 513 * parentWidth/childWidth are positive and childAt can be negative. 514 */ 515 private int positionAdjustment(int parentWidth, int childWidth, int childAt) { 516 517 // +-----+ 518 // | --- | No Change 519 // +-----+ 520 if (childAt >= 0 && childWidth + childAt <= parentWidth) { 521 return 0; 522 } 523 524 // +-----+ 525 // --------- No Change 526 // +-----+ 527 if (childAt <= 0 && childWidth + childAt >= parentWidth) { 528 return 0; 529 } 530 531 // +-----+ +-----+ 532 // | ---- -> | ----| 533 // +-----+ +-----+ 534 if (childAt > 0 && childWidth <= parentWidth) { 535 return -childAt + parentWidth - childWidth; 536 } 537 538 // +-----+ +-----+ 539 // | -------- -> |-------- 540 // +-----+ +-----+ 541 if (childAt >= 0 && childWidth >= parentWidth) { 542 return -childAt; 543 } 544 545 // +-----+ +-----+ 546 // ---- | -> |---- | 547 // +-----+ +-----+ 548 if (childAt <= 0 && childWidth <= parentWidth) { 549 return -childAt; 550 } 551 552 // +-----+ +-----+ 553 //-------- | -> --------| 554 // +-----+ +-----+ 555 if (childAt < 0 && childWidth >= parentWidth) { 556 return -childAt + parentWidth - childWidth; 557 } 558 559 return 0; 560 } 561 562 563 /** 564 * The viewport "scrolls" its child (called the "view") by the 565 * normal parent/child clipping (typically the view is moved in 566 * the opposite direction of the scroll). A non-<code>null</code> border, 567 * or non-zero insets, isn't supported, to prevent the geometry 568 * of this component from becoming complex enough to inhibit 569 * subclassing. To create a <code>JViewport</code> with a border, 570 * add it to a <code>JPanel</code> that has a border. 571 * <p>Note: If <code>border</code> is non-<code>null</code>, this 572 * method will throw an exception as borders are not supported on 573 * a <code>JViewPort</code>. 574 * 575 * @param border the <code>Border</code> to set 576 * @exception IllegalArgumentException this method is not implemented 577 */ 578 public final void setBorder(Border border) { 579 if (border != null) { 580 throw new IllegalArgumentException("JViewport.setBorder() not supported"); 581 } 582 } 583 584 585 /** 586 * Returns the insets (border) dimensions as (0,0,0,0), since borders 587 * are not supported on a <code>JViewport</code>. 588 * 589 * @return a <code>Rectangle</code> of zero dimension and zero origin 590 * @see #setBorder 591 */ 592 public final Insets getInsets() { 593 return new Insets(0, 0, 0, 0); 594 } 595 596 /** 597 * Returns an <code>Insets</code> object containing this 598 * <code>JViewport</code>s inset values. The passed-in 599 * <code>Insets</code> object will be reinitialized, and 600 * all existing values within this object are overwritten. 601 * 602 * @param insets the <code>Insets</code> object which can be reused 603 * @return this viewports inset values 604 * @see #getInsets 605 * @beaninfo 606 * expert: true 607 */ 608 public final Insets getInsets(Insets insets) { 609 insets.left = insets.top = insets.right = insets.bottom = 0; 610 return insets; 611 } 612 613 614 private Graphics getBackingStoreGraphics(Graphics g) { 615 Graphics bsg = backingStoreImage.getGraphics(); 616 bsg.setColor(g.getColor()); 617 bsg.setFont(g.getFont()); 618 bsg.setClip(g.getClipBounds()); 619 return bsg; 620 } 621 622 623 private void paintViaBackingStore(Graphics g) { 624 Graphics bsg = getBackingStoreGraphics(g); 625 try { 626 super.paint(bsg); 627 g.drawImage(backingStoreImage, 0, 0, this); 628 } finally { 629 bsg.dispose(); 630 } 631 } 632 633 private void paintViaBackingStore(Graphics g, Rectangle oClip) { 634 Graphics bsg = getBackingStoreGraphics(g); 635 try { 636 super.paint(bsg); 637 g.setClip(oClip); 638 g.drawImage(backingStoreImage, 0, 0, this); 639 } finally { 640 bsg.dispose(); 641 } 642 } 643 644 /** 645 * The <code>JViewport</code> overrides the default implementation of 646 * this method (in <code>JComponent</code>) to return false. 647 * This ensures 648 * that the drawing machinery will call the <code>Viewport</code>'s 649 * <code>paint</code> 650 * implementation rather than messaging the <code>JViewport</code>'s 651 * children directly. 652 * 653 * @return false 654 */ 655 public boolean isOptimizedDrawingEnabled() { 656 return false; 657 } 658 659 /** 660 * Returns true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE} to cause 661 * painting to originate from {@code JViewport}, or one of its 662 * ancestors. Otherwise returns {@code false}. 663 * 664 * @return true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE}. 665 * @see JComponent#isPaintingOrigin() 666 */ 667 protected boolean isPaintingOrigin() { 668 return scrollMode == BACKINGSTORE_SCROLL_MODE; 669 } 670 671 672 /** 673 * Only used by the paint method below. 674 */ 675 private Point getViewLocation() { 676 Component view = getView(); 677 if (view != null) { 678 return view.getLocation(); 679 } 680 else { 681 return new Point(0,0); 682 } 683 } 684 685 /** 686 * Depending on whether the <code>backingStore</code> is enabled, 687 * either paint the image through the backing store or paint 688 * just the recently exposed part, using the backing store 689 * to "blit" the remainder. 690 * <blockquote> 691 * The term "blit" is the pronounced version of the PDP-10 692 * BLT (BLock Transfer) instruction, which copied a block of 693 * bits. (In case you were curious.) 694 * </blockquote> 695 * 696 * @param g the <code>Graphics</code> context within which to paint 697 */ 698 public void paint(Graphics g) 699 { 700 int width = getWidth(); 701 int height = getHeight(); 702 703 if ((width <= 0) || (height <= 0)) { 704 return; 705 } 706 707 if (inBlitPaint) { 708 // We invoked paint as part of copyArea cleanup, let it through. 709 super.paint(g); 710 return; 711 } 712 713 if (repaintAll) { 714 repaintAll = false; 715 Rectangle clipB = g.getClipBounds(); 716 if (clipB.width < getWidth() || 717 clipB.height < getHeight()) { 718 waitingForRepaint = true; 719 if (repaintTimer == null) { 720 repaintTimer = createRepaintTimer(); 721 } 722 repaintTimer.stop(); 723 repaintTimer.start(); 724 // We really don't need to paint, a future repaint will 725 // take care of it, but if we don't we get an ugly flicker. 726 } 727 else { 728 if (repaintTimer != null) { 729 repaintTimer.stop(); 730 } 731 waitingForRepaint = false; 732 } 733 } 734 else if (waitingForRepaint) { 735 // Need a complete repaint before resetting waitingForRepaint 736 Rectangle clipB = g.getClipBounds(); 737 if (clipB.width >= getWidth() && 738 clipB.height >= getHeight()) { 739 waitingForRepaint = false; 740 repaintTimer.stop(); 741 } 742 } 743 744 if (!backingStore || isBlitting() || getView() == null) { 745 super.paint(g); 746 lastPaintPosition = getViewLocation(); 747 return; 748 } 749 750 // If the view is smaller than the viewport and we are not opaque 751 // (that is, we won't paint our background), we should set the 752 // clip. Otherwise, as the bounds of the view vary, we will 753 // blit garbage into the exposed areas. 754 Rectangle viewBounds = getView().getBounds(); 755 if (!isOpaque()) { 756 g.clipRect(0, 0, viewBounds.width, viewBounds.height); 757 } 758 759 if (backingStoreImage == null) { 760 // Backing store is enabled but this is the first call to paint. 761 // Create the backing store, paint it and then copy to g. 762 // The backing store image will be created with the size of 763 // the viewport. We must make sure the clip region is the 764 // same size, otherwise when scrolling the backing image 765 // the region outside of the clipped region will not be painted, 766 // and result in empty areas. 767 backingStoreImage = createImage(width, height); 768 Rectangle clip = g.getClipBounds(); 769 if (clip.width != width || clip.height != height) { 770 if (!isOpaque()) { 771 g.setClip(0, 0, Math.min(viewBounds.width, width), 772 Math.min(viewBounds.height, height)); 773 } 774 else { 775 g.setClip(0, 0, width, height); 776 } 777 paintViaBackingStore(g, clip); 778 } 779 else { 780 paintViaBackingStore(g); 781 } 782 } 783 else { 784 if (!scrollUnderway || lastPaintPosition.equals(getViewLocation())) { 785 // No scrolling happened: repaint required area via backing store. 786 paintViaBackingStore(g); 787 } else { 788 // The image was scrolled. Manipulate the backing store and flush it to g. 789 Point blitFrom = new Point(); 790 Point blitTo = new Point(); 791 Dimension blitSize = new Dimension(); 792 Rectangle blitPaint = new Rectangle(); 793 794 Point newLocation = getViewLocation(); 795 int dx = newLocation.x - lastPaintPosition.x; 796 int dy = newLocation.y - lastPaintPosition.y; 797 boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, blitPaint); 798 if (!canBlit) { 799 // The image was either moved diagonally or 800 // moved by more than the image size: paint normally. 801 paintViaBackingStore(g); 802 } else { 803 int bdx = blitTo.x - blitFrom.x; 804 int bdy = blitTo.y - blitFrom.y; 805 806 // Move the relevant part of the backing store. 807 Rectangle clip = g.getClipBounds(); 808 // We don't want to inherit the clip region when copying 809 // bits, if it is inherited it will result in not moving 810 // all of the image resulting in garbage appearing on 811 // the screen. 812 g.setClip(0, 0, width, height); 813 Graphics bsg = getBackingStoreGraphics(g); 814 try { 815 bsg.copyArea(blitFrom.x, blitFrom.y, blitSize.width, blitSize.height, bdx, bdy); 816 817 g.setClip(clip.x, clip.y, clip.width, clip.height); 818 // Paint the rest of the view; the part that has just been exposed. 819 Rectangle r = viewBounds.intersection(blitPaint); 820 bsg.setClip(r); 821 super.paint(bsg); 822 823 // Copy whole of the backing store to g. 824 g.drawImage(backingStoreImage, 0, 0, this); 825 } finally { 826 bsg.dispose(); 827 } 828 } 829 } 830 } 831 lastPaintPosition = getViewLocation(); 832 scrollUnderway = false; 833 } 834 835 836 /** 837 * Sets the bounds of this viewport. If the viewport's width 838 * or height has changed, fire a <code>StateChanged</code> event. 839 * 840 * @param x left edge of the origin 841 * @param y top edge of the origin 842 * @param w width in pixels 843 * @param h height in pixels 844 * 845 * @see JComponent#reshape(int, int, int, int) 846 */ 847 public void reshape(int x, int y, int w, int h) { 848 boolean sizeChanged = (getWidth() != w) || (getHeight() != h); 849 if (sizeChanged) { 850 backingStoreImage = null; 851 } 852 super.reshape(x, y, w, h); 853 if (sizeChanged || viewChanged) { 854 viewChanged = false; 855 856 fireStateChanged(); 857 } 858 } 859 860 861 /** 862 * Used to control the method of scrolling the viewport contents. 863 * You may want to change this mode to get maximum performance for your 864 * use case. 865 * 866 * @param mode one of the following values: 867 * <ul> 868 * <li> JViewport.BLIT_SCROLL_MODE 869 * <li> JViewport.BACKINGSTORE_SCROLL_MODE 870 * <li> JViewport.SIMPLE_SCROLL_MODE 871 * </ul> 872 * 873 * @see #BLIT_SCROLL_MODE 874 * @see #BACKINGSTORE_SCROLL_MODE 875 * @see #SIMPLE_SCROLL_MODE 876 * 877 * @beaninfo 878 * bound: false 879 * description: Method of moving contents for incremental scrolls. 880 * enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE 881 * BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE 882 * SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE 883 * 884 * @since 1.3 885 */ 886 public void setScrollMode(int mode) { 887 scrollMode = mode; 888 backingStore = mode == BACKINGSTORE_SCROLL_MODE; 889 } 890 891 /** 892 * Returns the current scrolling mode. 893 * 894 * @return the <code>scrollMode</code> property 895 * @see #setScrollMode 896 * @since 1.3 897 */ 898 public int getScrollMode() { 899 return scrollMode; 900 } 901 902 /** 903 * Returns <code>true</code> if this viewport is maintaining 904 * an offscreen image of its contents. 905 * 906 * @return <code>true</code> if <code>scrollMode</code> is 907 * <code>BACKINGSTORE_SCROLL_MODE</code> 908 * 909 * @deprecated As of Java 2 platform v1.3, replaced by 910 * <code>getScrollMode()</code>. 911 */ 912 @Deprecated 913 public boolean isBackingStoreEnabled() { 914 return scrollMode == BACKINGSTORE_SCROLL_MODE; 915 } 916 917 918 /** 919 * If true if this viewport will maintain an offscreen 920 * image of its contents. The image is used to reduce the cost 921 * of small one dimensional changes to the <code>viewPosition</code>. 922 * Rather than repainting the entire viewport we use 923 * <code>Graphics.copyArea</code> to effect some of the scroll. 924 * 925 * @param enabled if true, maintain an offscreen backing store 926 * 927 * @deprecated As of Java 2 platform v1.3, replaced by 928 * <code>setScrollMode()</code>. 929 */ 930 @Deprecated 931 public void setBackingStoreEnabled(boolean enabled) { 932 if (enabled) { 933 setScrollMode(BACKINGSTORE_SCROLL_MODE); 934 } else { 935 setScrollMode(BLIT_SCROLL_MODE); 936 } 937 } 938 939 private boolean isBlitting() { 940 Component view = getView(); 941 return (scrollMode == BLIT_SCROLL_MODE) && 942 (view instanceof JComponent) && view.isOpaque(); 943 } 944 945 946 /** 947 * Returns the <code>JViewport</code>'s one child or <code>null</code>. 948 * 949 * @return the viewports child, or <code>null</code> if none exists 950 * 951 * @see #setView 952 */ 953 public Component getView() { 954 return (getComponentCount() > 0) ? getComponent(0) : null; 955 } 956 957 /** 958 * Sets the <code>JViewport</code>'s one lightweight child 959 * (<code>view</code>), which can be <code>null</code>. 960 * 961 * @param view the viewport's new lightweight child 962 * 963 * @see #getView 964 */ 965 public void setView(Component view) { 966 967 /* Remove the viewport's existing children, if any. 968 * Note that removeAll() isn't used here because it 969 * doesn't call remove() (which JViewport overrides). 970 */ 971 int n = getComponentCount(); 972 for(int i = n - 1; i >= 0; i--) { 973 remove(getComponent(i)); 974 } 975 976 isViewSizeSet = false; 977 978 if (view != null) { 979 super.addImpl(view, null, -1); 980 viewListener = createViewListener(); 981 view.addComponentListener(viewListener); 982 } 983 984 if (hasHadValidView) { 985 // Only fire a change if a view has been installed. 986 fireStateChanged(); 987 } 988 else if (view != null) { 989 hasHadValidView = true; 990 } 991 992 viewChanged = true; 993 994 revalidate(); 995 repaint(); 996 } 997 998 999 /** 1000 * If the view's size hasn't been explicitly set, return the 1001 * preferred size, otherwise return the view's current size. 1002 * If there is no view, return 0,0. 1003 * 1004 * @return a <code>Dimension</code> object specifying the size of the view 1005 */ 1006 public Dimension getViewSize() { 1007 Component view = getView(); 1008 1009 if (view == null) { 1010 return new Dimension(0,0); 1011 } 1012 else if (isViewSizeSet) { 1013 return view.getSize(); 1014 } 1015 else { 1016 return view.getPreferredSize(); 1017 } 1018 } 1019 1020 1021 /** 1022 * Sets the size of the view. A state changed event will be fired. 1023 * 1024 * @param newSize a <code>Dimension</code> object specifying the new 1025 * size of the view 1026 */ 1027 public void setViewSize(Dimension newSize) { 1028 Component view = getView(); 1029 if (view != null) { 1030 Dimension oldSize = view.getSize(); 1031 if (!newSize.equals(oldSize)) { 1032 // scrollUnderway will be true if this is invoked as the 1033 // result of a validate and setViewPosition was previously 1034 // invoked. 1035 scrollUnderway = false; 1036 view.setSize(newSize); 1037 isViewSizeSet = true; 1038 fireStateChanged(); 1039 } 1040 } 1041 } 1042 1043 /** 1044 * Returns the view coordinates that appear in the upper left 1045 * hand corner of the viewport, or 0,0 if there's no view. 1046 * 1047 * @return a <code>Point</code> object giving the upper left coordinates 1048 */ 1049 public Point getViewPosition() { 1050 Component view = getView(); 1051 if (view != null) { 1052 Point p = view.getLocation(); 1053 p.x = -p.x; 1054 p.y = -p.y; 1055 return p; 1056 } 1057 else { 1058 return new Point(0,0); 1059 } 1060 } 1061 1062 1063 /** 1064 * Sets the view coordinates that appear in the upper left 1065 * hand corner of the viewport, does nothing if there's no view. 1066 * 1067 * @param p a <code>Point</code> object giving the upper left coordinates 1068 */ 1069 public void setViewPosition(Point p) 1070 { 1071 Component view = getView(); 1072 if (view == null) { 1073 return; 1074 } 1075 1076 int oldX, oldY, x = p.x, y = p.y; 1077 1078 /* Collect the old x,y values for the views location 1079 * and do the song and dance to avoid allocating 1080 * a Rectangle object if we don't have to. 1081 */ 1082 if (view instanceof JComponent) { 1083 JComponent c = (JComponent)view; 1084 oldX = c.getX(); 1085 oldY = c.getY(); 1086 } 1087 else { 1088 Rectangle r = view.getBounds(); 1089 oldX = r.x; 1090 oldY = r.y; 1091 } 1092 1093 /* The view scrolls in the opposite direction to mouse 1094 * movement. 1095 */ 1096 int newX = -x; 1097 int newY = -y; 1098 1099 if ((oldX != newX) || (oldY != newY)) { 1100 if (!waitingForRepaint && isBlitting() && canUseWindowBlitter()) { 1101 RepaintManager rm = RepaintManager.currentManager(this); 1102 // The cast to JComponent will work, if view is not 1103 // a JComponent, isBlitting will return false. 1104 JComponent jview = (JComponent)view; 1105 Rectangle dirty = rm.getDirtyRegion(jview); 1106 if (dirty == null || !dirty.contains(jview.getVisibleRect())) { 1107 rm.beginPaint(); 1108 try { 1109 Graphics g = JComponent.safelyGetGraphics(this); 1110 flushViewDirtyRegion(g, dirty); 1111 view.setLocation(newX, newY); 1112 g.setClip(0,0,getWidth(), Math.min(getHeight(), 1113 jview.getHeight())); 1114 // Repaint the complete component if the blit succeeded 1115 // and needsRepaintAfterBlit returns true. 1116 repaintAll = (windowBlitPaint(g) && 1117 needsRepaintAfterBlit()); 1118 g.dispose(); 1119 rm.markCompletelyClean((JComponent)getParent()); 1120 rm.markCompletelyClean(this); 1121 rm.markCompletelyClean(jview); 1122 } finally { 1123 rm.endPaint(); 1124 } 1125 } 1126 else { 1127 // The visible region is dirty, no point in doing copyArea 1128 view.setLocation(newX, newY); 1129 repaintAll = false; 1130 } 1131 } 1132 else { 1133 scrollUnderway = true; 1134 // This calls setBounds(), and then repaint(). 1135 view.setLocation(newX, newY); 1136 repaintAll = false; 1137 } 1138 // we must validate the hierarchy to not break the hw/lw mixing 1139 revalidate(); 1140 fireStateChanged(); 1141 } 1142 } 1143 1144 1145 /** 1146 * Returns a rectangle whose origin is <code>getViewPosition</code> 1147 * and size is <code>getExtentSize</code>. 1148 * This is the visible part of the view, in view coordinates. 1149 * 1150 * @return a <code>Rectangle</code> giving the visible part of 1151 * the view using view coordinates. 1152 */ 1153 public Rectangle getViewRect() { 1154 return new Rectangle(getViewPosition(), getExtentSize()); 1155 } 1156 1157 1158 /** 1159 * Computes the parameters for a blit where the backing store image 1160 * currently contains <code>oldLoc</code> in the upper left hand corner 1161 * and we're scrolling to <code>newLoc</code>. 1162 * The parameters are modified 1163 * to return the values required for the blit. 1164 * 1165 * @param dx the horizontal delta 1166 * @param dy the vertical delta 1167 * @param blitFrom the <code>Point</code> we're blitting from 1168 * @param blitTo the <code>Point</code> we're blitting to 1169 * @param blitSize the <code>Dimension</code> of the area to blit 1170 * @param blitPaint the area to blit 1171 * @return true if the parameters are modified and we're ready to blit; 1172 * false otherwise 1173 */ 1174 protected boolean computeBlit( 1175 int dx, 1176 int dy, 1177 Point blitFrom, 1178 Point blitTo, 1179 Dimension blitSize, 1180 Rectangle blitPaint) 1181 { 1182 int dxAbs = Math.abs(dx); 1183 int dyAbs = Math.abs(dy); 1184 Dimension extentSize = getExtentSize(); 1185 1186 if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) { 1187 if (dy < 0) { 1188 blitFrom.y = -dy; 1189 blitTo.y = 0; 1190 blitPaint.y = extentSize.height + dy; 1191 } 1192 else { 1193 blitFrom.y = 0; 1194 blitTo.y = dy; 1195 blitPaint.y = 0; 1196 } 1197 1198 blitPaint.x = blitFrom.x = blitTo.x = 0; 1199 1200 blitSize.width = extentSize.width; 1201 blitSize.height = extentSize.height - dyAbs; 1202 1203 blitPaint.width = extentSize.width; 1204 blitPaint.height = dyAbs; 1205 1206 return true; 1207 } 1208 1209 else if ((dy == 0) && (dx != 0) && (dxAbs < extentSize.width)) { 1210 if (dx < 0) { 1211 blitFrom.x = -dx; 1212 blitTo.x = 0; 1213 blitPaint.x = extentSize.width + dx; 1214 } 1215 else { 1216 blitFrom.x = 0; 1217 blitTo.x = dx; 1218 blitPaint.x = 0; 1219 } 1220 1221 blitPaint.y = blitFrom.y = blitTo.y = 0; 1222 1223 blitSize.width = extentSize.width - dxAbs; 1224 blitSize.height = extentSize.height; 1225 1226 blitPaint.width = dxAbs; 1227 blitPaint.height = extentSize.height; 1228 1229 return true; 1230 } 1231 1232 else { 1233 return false; 1234 } 1235 } 1236 1237 1238 /** 1239 * Returns the size of the visible part of the view in view coordinates. 1240 * 1241 * @return a <code>Dimension</code> object giving the size of the view 1242 */ 1243 @Transient 1244 public Dimension getExtentSize() { 1245 return getSize(); 1246 } 1247 1248 1249 /** 1250 * Converts a size in pixel coordinates to view coordinates. 1251 * Subclasses of viewport that support "logical coordinates" 1252 * will override this method. 1253 * 1254 * @param size a <code>Dimension</code> object using pixel coordinates 1255 * @return a <code>Dimension</code> object converted to view coordinates 1256 */ 1257 public Dimension toViewCoordinates(Dimension size) { 1258 return new Dimension(size); 1259 } 1260 1261 /** 1262 * Converts a point in pixel coordinates to view coordinates. 1263 * Subclasses of viewport that support "logical coordinates" 1264 * will override this method. 1265 * 1266 * @param p a <code>Point</code> object using pixel coordinates 1267 * @return a <code>Point</code> object converted to view coordinates 1268 */ 1269 public Point toViewCoordinates(Point p) { 1270 return new Point(p); 1271 } 1272 1273 1274 /** 1275 * Sets the size of the visible part of the view using view coordinates. 1276 * 1277 * @param newExtent a <code>Dimension</code> object specifying 1278 * the size of the view 1279 */ 1280 public void setExtentSize(Dimension newExtent) { 1281 Dimension oldExtent = getExtentSize(); 1282 if (!newExtent.equals(oldExtent)) { 1283 setSize(newExtent); 1284 fireStateChanged(); 1285 } 1286 } 1287 1288 /** 1289 * A listener for the view. 1290 * <p> 1291 * <strong>Warning:</strong> 1292 * Serialized objects of this class will not be compatible with 1293 * future Swing releases. The current serialization support is 1294 * appropriate for short term storage or RMI between applications running 1295 * the same version of Swing. As of 1.4, support for long term storage 1296 * of all JavaBeans™ 1297 * has been added to the <code>java.beans</code> package. 1298 * Please see {@link java.beans.XMLEncoder}. 1299 */ 1300 @SuppressWarnings("serial") // Same-version serialization only 1301 protected class ViewListener extends ComponentAdapter implements Serializable 1302 { 1303 public void componentResized(ComponentEvent e) { 1304 fireStateChanged(); 1305 revalidate(); 1306 } 1307 } 1308 1309 /** 1310 * Creates a listener for the view. 1311 * @return a <code>ViewListener</code> 1312 */ 1313 protected ViewListener createViewListener() { 1314 return new ViewListener(); 1315 } 1316 1317 1318 /** 1319 * Subclassers can override this to install a different 1320 * layout manager (or <code>null</code>) in the constructor. Returns 1321 * the <code>LayoutManager</code> to install on the <code>JViewport</code>. 1322 * 1323 * @return a <code>LayoutManager</code> 1324 */ 1325 protected LayoutManager createLayoutManager() { 1326 return ViewportLayout.SHARED_INSTANCE; 1327 } 1328 1329 1330 /** 1331 * Adds a <code>ChangeListener</code> to the list that is 1332 * notified each time the view's 1333 * size, position, or the viewport's extent size has changed. 1334 * 1335 * @param l the <code>ChangeListener</code> to add 1336 * @see #removeChangeListener 1337 * @see #setViewPosition 1338 * @see #setViewSize 1339 * @see #setExtentSize 1340 */ 1341 public void addChangeListener(ChangeListener l) { 1342 listenerList.add(ChangeListener.class, l); 1343 } 1344 1345 /** 1346 * Removes a <code>ChangeListener</code> from the list that's notified each 1347 * time the views size, position, or the viewports extent size 1348 * has changed. 1349 * 1350 * @param l the <code>ChangeListener</code> to remove 1351 * @see #addChangeListener 1352 */ 1353 public void removeChangeListener(ChangeListener l) { 1354 listenerList.remove(ChangeListener.class, l); 1355 } 1356 1357 /** 1358 * Returns an array of all the <code>ChangeListener</code>s added 1359 * to this JViewport with addChangeListener(). 1360 * 1361 * @return all of the <code>ChangeListener</code>s added or an empty 1362 * array if no listeners have been added 1363 * @since 1.4 1364 */ 1365 public ChangeListener[] getChangeListeners() { 1366 return listenerList.getListeners(ChangeListener.class); 1367 } 1368 1369 /** 1370 * Notifies all <code>ChangeListeners</code> when the views 1371 * size, position, or the viewports extent size has changed. 1372 * 1373 * @see #addChangeListener 1374 * @see #removeChangeListener 1375 * @see EventListenerList 1376 */ 1377 protected void fireStateChanged() 1378 { 1379 Object[] listeners = listenerList.getListenerList(); 1380 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1381 if (listeners[i] == ChangeListener.class) { 1382 if (changeEvent == null) { 1383 changeEvent = new ChangeEvent(this); 1384 } 1385 ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent); 1386 } 1387 } 1388 } 1389 1390 /** 1391 * Always repaint in the parents coordinate system to make sure 1392 * only one paint is performed by the <code>RepaintManager</code>. 1393 * 1394 * @param tm maximum time in milliseconds before update 1395 * @param x the <code>x</code> coordinate (pixels over from left) 1396 * @param y the <code>y</code> coordinate (pixels down from top) 1397 * @param w the width 1398 * @param h the height 1399 * @see java.awt.Component#update(java.awt.Graphics) 1400 */ 1401 public void repaint(long tm, int x, int y, int w, int h) { 1402 Container parent = getParent(); 1403 if(parent != null) 1404 parent.repaint(tm,x+getX(),y+getY(),w,h); 1405 else 1406 super.repaint(tm,x,y,w,h); 1407 } 1408 1409 1410 /** 1411 * Returns a string representation of this <code>JViewport</code>. 1412 * This method 1413 * is intended to be used only for debugging purposes, and the 1414 * content and format of the returned string may vary between 1415 * implementations. The returned string may be empty but may not 1416 * be <code>null</code>. 1417 * 1418 * @return a string representation of this <code>JViewport</code> 1419 */ 1420 protected String paramString() { 1421 String isViewSizeSetString = (isViewSizeSet ? 1422 "true" : "false"); 1423 String lastPaintPositionString = (lastPaintPosition != null ? 1424 lastPaintPosition.toString() : ""); 1425 String scrollUnderwayString = (scrollUnderway ? 1426 "true" : "false"); 1427 1428 return super.paramString() + 1429 ",isViewSizeSet=" + isViewSizeSetString + 1430 ",lastPaintPosition=" + lastPaintPositionString + 1431 ",scrollUnderway=" + scrollUnderwayString; 1432 } 1433 1434 // 1435 // Following is used when doBlit is true. 1436 // 1437 1438 /** 1439 * Notifies listeners of a property change. This is subclassed to update 1440 * the <code>windowBlit</code> property. 1441 * (The <code>putClientProperty</code> property is final). 1442 * 1443 * @param propertyName a string containing the property name 1444 * @param oldValue the old value of the property 1445 * @param newValue the new value of the property 1446 */ 1447 protected void firePropertyChange(String propertyName, Object oldValue, 1448 Object newValue) { 1449 super.firePropertyChange(propertyName, oldValue, newValue); 1450 if (propertyName.equals(EnableWindowBlit)) { 1451 if (newValue != null) { 1452 setScrollMode(BLIT_SCROLL_MODE); 1453 } else { 1454 setScrollMode(SIMPLE_SCROLL_MODE); 1455 } 1456 } 1457 } 1458 1459 /** 1460 * Returns true if the component needs to be completely repainted after 1461 * a blit and a paint is received. 1462 */ 1463 private boolean needsRepaintAfterBlit() { 1464 // Find the first heavy weight ancestor. isObscured and 1465 // canDetermineObscurity are only appropriate for heavy weights. 1466 Component heavyParent = getParent(); 1467 1468 while (heavyParent != null && heavyParent.isLightweight()) { 1469 heavyParent = heavyParent.getParent(); 1470 } 1471 1472 if (heavyParent != null) { 1473 ComponentPeer peer = heavyParent.getPeer(); 1474 1475 if (peer != null && peer.canDetermineObscurity() && 1476 !peer.isObscured()) { 1477 // The peer says we aren't obscured, therefore we can assume 1478 // that we won't later be messaged to paint a portion that 1479 // we tried to blit that wasn't valid. 1480 // It is certainly possible that when we blited we were 1481 // obscured, and by the time this is invoked we aren't, but the 1482 // chances of that happening are pretty slim. 1483 return false; 1484 } 1485 } 1486 return true; 1487 } 1488 1489 private Timer createRepaintTimer() { 1490 Timer timer = new Timer(300, new ActionListener() { 1491 public void actionPerformed(ActionEvent ae) { 1492 // waitingForRepaint will be false if a paint came down 1493 // with the complete clip rect, in which case we don't 1494 // have to cause a repaint. 1495 if (waitingForRepaint) { 1496 repaint(); 1497 } 1498 } 1499 }); 1500 timer.setRepeats(false); 1501 return timer; 1502 } 1503 1504 /** 1505 * If the repaint manager has a dirty region for the view, the view is 1506 * asked to paint. 1507 * 1508 * @param g the <code>Graphics</code> context within which to paint 1509 */ 1510 private void flushViewDirtyRegion(Graphics g, Rectangle dirty) { 1511 JComponent view = (JComponent) getView(); 1512 if(dirty != null && dirty.width > 0 && dirty.height > 0) { 1513 dirty.x += view.getX(); 1514 dirty.y += view.getY(); 1515 Rectangle clip = g.getClipBounds(); 1516 if (clip == null) { 1517 // Only happens in 1.2 1518 g.setClip(0, 0, getWidth(), getHeight()); 1519 } 1520 g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height); 1521 clip = g.getClipBounds(); 1522 // Only paint the dirty region if it is visible. 1523 if (clip.width > 0 && clip.height > 0) { 1524 paintView(g); 1525 } 1526 } 1527 } 1528 1529 /** 1530 * Used when blitting. 1531 * 1532 * @param g the <code>Graphics</code> context within which to paint 1533 * @return true if blitting succeeded; otherwise false 1534 */ 1535 private boolean windowBlitPaint(Graphics g) { 1536 int width = getWidth(); 1537 int height = getHeight(); 1538 1539 if ((width == 0) || (height == 0)) { 1540 return false; 1541 } 1542 1543 boolean retValue; 1544 RepaintManager rm = RepaintManager.currentManager(this); 1545 JComponent view = (JComponent) getView(); 1546 1547 if (lastPaintPosition == null || 1548 lastPaintPosition.equals(getViewLocation())) { 1549 paintView(g); 1550 retValue = false; 1551 } else { 1552 // The image was scrolled. Manipulate the backing store and flush 1553 // it to g. 1554 Point blitFrom = new Point(); 1555 Point blitTo = new Point(); 1556 Dimension blitSize = new Dimension(); 1557 Rectangle blitPaint = new Rectangle(); 1558 1559 Point newLocation = getViewLocation(); 1560 int dx = newLocation.x - lastPaintPosition.x; 1561 int dy = newLocation.y - lastPaintPosition.y; 1562 boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, 1563 blitPaint); 1564 if (!canBlit) { 1565 paintView(g); 1566 retValue = false; 1567 } else { 1568 // Prepare the rest of the view; the part that has just been 1569 // exposed. 1570 Rectangle r = view.getBounds().intersection(blitPaint); 1571 r.x -= view.getX(); 1572 r.y -= view.getY(); 1573 1574 blitDoubleBuffered(view, g, r.x, r.y, r.width, r.height, 1575 blitFrom.x, blitFrom.y, blitTo.x, blitTo.y, 1576 blitSize.width, blitSize.height); 1577 retValue = true; 1578 } 1579 } 1580 lastPaintPosition = getViewLocation(); 1581 return retValue; 1582 } 1583 1584 // 1585 // NOTE: the code below uses paintForceDoubleBuffered for historical 1586 // reasons. If we're going to allow a blit we've already accounted for 1587 // everything that paintImmediately and _paintImmediately does, for that 1588 // reason we call into paintForceDoubleBuffered to diregard whether or 1589 // not setDoubleBuffered(true) was invoked on the view. 1590 // 1591 1592 private void blitDoubleBuffered(JComponent view, Graphics g, 1593 int clipX, int clipY, int clipW, int clipH, 1594 int blitFromX, int blitFromY, int blitToX, int blitToY, 1595 int blitW, int blitH) { 1596 // NOTE: 1597 // blitFrom/blitTo are in JViewport coordinates system 1598 // not the views coordinate space. 1599 // clip* are in the views coordinate space. 1600 RepaintManager rm = RepaintManager.currentManager(this); 1601 int bdx = blitToX - blitFromX; 1602 int bdy = blitToY - blitFromY; 1603 1604 Composite oldComposite = null; 1605 // Shift the scrolled region 1606 if (g instanceof Graphics2D) { 1607 Graphics2D g2d = (Graphics2D) g; 1608 oldComposite = g2d.getComposite(); 1609 g2d.setComposite(AlphaComposite.Src); 1610 } 1611 rm.copyArea(this, g, blitFromX, blitFromY, blitW, blitH, bdx, bdy, 1612 false); 1613 if (oldComposite != null) { 1614 ((Graphics2D) g).setComposite(oldComposite); 1615 } 1616 // Paint the newly exposed region. 1617 int x = view.getX(); 1618 int y = view.getY(); 1619 g.translate(x, y); 1620 g.setClip(clipX, clipY, clipW, clipH); 1621 view.paintForceDoubleBuffered(g); 1622 g.translate(-x, -y); 1623 } 1624 1625 /** 1626 * Called to paint the view, usually when <code>blitPaint</code> 1627 * can not blit. 1628 * 1629 * @param g the <code>Graphics</code> context within which to paint 1630 */ 1631 private void paintView(Graphics g) { 1632 Rectangle clip = g.getClipBounds(); 1633 JComponent view = (JComponent)getView(); 1634 1635 if (view.getWidth() >= getWidth()) { 1636 // Graphics is relative to JViewport, need to map to view's 1637 // coordinates space. 1638 int x = view.getX(); 1639 int y = view.getY(); 1640 g.translate(x, y); 1641 g.setClip(clip.x - x, clip.y - y, clip.width, clip.height); 1642 view.paintForceDoubleBuffered(g); 1643 g.translate(-x, -y); 1644 g.setClip(clip.x, clip.y, clip.width, clip.height); 1645 } 1646 else { 1647 // To avoid any problems that may result from the viewport being 1648 // bigger than the view we start painting from the viewport. 1649 try { 1650 inBlitPaint = true; 1651 paintForceDoubleBuffered(g); 1652 } finally { 1653 inBlitPaint = false; 1654 } 1655 } 1656 } 1657 1658 /** 1659 * Returns true if the viewport is not obscured by one of its ancestors, 1660 * or its ancestors children and if the viewport is showing. Blitting 1661 * when the view isn't showing will work, 1662 * or rather <code>copyArea</code> will work, 1663 * but will not produce the expected behavior. 1664 */ 1665 private boolean canUseWindowBlitter() { 1666 if (!isShowing() || (!(getParent() instanceof JComponent) && 1667 !(getView() instanceof JComponent))) { 1668 return false; 1669 } 1670 if (isPainting()) { 1671 // We're in the process of painting, don't blit. If we were 1672 // to blit we would draw on top of what we're already drawing, 1673 // so bail. 1674 return false; 1675 } 1676 1677 Rectangle dirtyRegion = RepaintManager.currentManager(this). 1678 getDirtyRegion((JComponent)getParent()); 1679 1680 if (dirtyRegion != null && dirtyRegion.width > 0 && 1681 dirtyRegion.height > 0) { 1682 // Part of the scrollpane needs to be repainted too, don't blit. 1683 return false; 1684 } 1685 1686 Rectangle clip = new Rectangle(0,0,getWidth(),getHeight()); 1687 Rectangle oldClip = new Rectangle(); 1688 Rectangle tmp2 = null; 1689 Container parent; 1690 Component lastParent = null; 1691 int x, y, w, h; 1692 1693 for(parent = this; parent != null && isLightweightComponent(parent); parent = parent.getParent()) { 1694 x = parent.getX(); 1695 y = parent.getY(); 1696 w = parent.getWidth(); 1697 h = parent.getHeight(); 1698 1699 oldClip.setBounds(clip); 1700 SwingUtilities.computeIntersection(0, 0, w, h, clip); 1701 if(!clip.equals(oldClip)) 1702 return false; 1703 1704 if(lastParent != null && parent instanceof JComponent && 1705 !((JComponent)parent).isOptimizedDrawingEnabled()) { 1706 Component comps[] = parent.getComponents(); 1707 int index = 0; 1708 1709 for(int i = comps.length - 1 ;i >= 0; i--) { 1710 if(comps[i] == lastParent) { 1711 index = i - 1; 1712 break; 1713 } 1714 } 1715 1716 while(index >= 0) { 1717 tmp2 = comps[index].getBounds(tmp2); 1718 1719 if(tmp2.intersects(clip)) 1720 return false; 1721 index--; 1722 } 1723 } 1724 clip.x += x; 1725 clip.y += y; 1726 lastParent = parent; 1727 } 1728 if (parent == null) { 1729 // No Window parent. 1730 return false; 1731 } 1732 return true; 1733 } 1734 1735 1736 ///////////////// 1737 // Accessibility support 1738 //////////////// 1739 1740 /** 1741 * Gets the AccessibleContext associated with this JViewport. 1742 * For viewports, the AccessibleContext takes the form of an 1743 * AccessibleJViewport. 1744 * A new AccessibleJViewport instance is created if necessary. 1745 * 1746 * @return an AccessibleJViewport that serves as the 1747 * AccessibleContext of this JViewport 1748 */ 1749 public AccessibleContext getAccessibleContext() { 1750 if (accessibleContext == null) { 1751 accessibleContext = new AccessibleJViewport(); 1752 } 1753 return accessibleContext; 1754 } 1755 1756 /** 1757 * This class implements accessibility support for the 1758 * <code>JViewport</code> class. It provides an implementation of the 1759 * Java Accessibility API appropriate to viewport user-interface elements. 1760 * <p> 1761 * <strong>Warning:</strong> 1762 * Serialized objects of this class will not be compatible with 1763 * future Swing releases. The current serialization support is 1764 * appropriate for short term storage or RMI between applications running 1765 * the same version of Swing. As of 1.4, support for long term storage 1766 * of all JavaBeans™ 1767 * has been added to the <code>java.beans</code> package. 1768 * Please see {@link java.beans.XMLEncoder}. 1769 */ 1770 @SuppressWarnings("serial") // Same-version serialization only 1771 protected class AccessibleJViewport extends AccessibleJComponent { 1772 /** 1773 * Get the role of this object. 1774 * 1775 * @return an instance of AccessibleRole describing the role of 1776 * the object 1777 */ 1778 public AccessibleRole getAccessibleRole() { 1779 return AccessibleRole.VIEWPORT; 1780 } 1781 } // inner class AccessibleJViewport 1782 }