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