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