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