1 /* 2 * Copyright (c) 1997, 2008, 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 fireStateChanged(); 1115 } 1116 } 1117 1118 1119 /** 1120 * Returns a rectangle whose origin is <code>getViewPosition</code> 1121 * and size is <code>getExtentSize</code>. 1122 * This is the visible part of the view, in view coordinates. 1123 * 1124 * @return a <code>Rectangle</code> giving the visible part of 1125 * the view using view coordinates. 1126 */ 1127 public Rectangle getViewRect() { 1128 return new Rectangle(getViewPosition(), getExtentSize()); 1129 } 1130 1131 1132 /** 1133 * Computes the parameters for a blit where the backing store image 1134 * currently contains <code>oldLoc</code> in the upper left hand corner 1135 * and we're scrolling to <code>newLoc</code>. 1136 * The parameters are modified 1137 * to return the values required for the blit. 1138 * 1139 * @param dx the horizontal delta 1140 * @param dy the vertical delta 1141 * @param blitFrom the <code>Point</code> we're blitting from 1142 * @param blitTo the <code>Point</code> we're blitting to 1143 * @param blitSize the <code>Dimension</code> of the area to blit 1144 * @param blitPaint the area to blit 1145 * @return true if the parameters are modified and we're ready to blit; 1146 * false otherwise 1147 */ 1148 protected boolean computeBlit( 1149 int dx, 1150 int dy, 1151 Point blitFrom, 1152 Point blitTo, 1153 Dimension blitSize, 1154 Rectangle blitPaint) 1155 { 1156 int dxAbs = Math.abs(dx); 1157 int dyAbs = Math.abs(dy); 1158 Dimension extentSize = getExtentSize(); 1159 1160 if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) { 1161 if (dy < 0) { 1162 blitFrom.y = -dy; 1163 blitTo.y = 0; 1164 blitPaint.y = extentSize.height + dy; 1165 } 1166 else { 1167 blitFrom.y = 0; 1168 blitTo.y = dy; 1169 blitPaint.y = 0; 1170 } 1171 1172 blitPaint.x = blitFrom.x = blitTo.x = 0; 1173 1174 blitSize.width = extentSize.width; 1175 blitSize.height = extentSize.height - dyAbs; 1176 1177 blitPaint.width = extentSize.width; 1178 blitPaint.height = dyAbs; 1179 1180 return true; 1181 } 1182 1183 else if ((dy == 0) && (dx != 0) && (dxAbs < extentSize.width)) { 1184 if (dx < 0) { 1185 blitFrom.x = -dx; 1186 blitTo.x = 0; 1187 blitPaint.x = extentSize.width + dx; 1188 } 1189 else { 1190 blitFrom.x = 0; 1191 blitTo.x = dx; 1192 blitPaint.x = 0; 1193 } 1194 1195 blitPaint.y = blitFrom.y = blitTo.y = 0; 1196 1197 blitSize.width = extentSize.width - dxAbs; 1198 blitSize.height = extentSize.height; 1199 1200 blitPaint.width = dxAbs; 1201 blitPaint.height = extentSize.height; 1202 1203 return true; 1204 } 1205 1206 else { 1207 return false; 1208 } 1209 } 1210 1211 1212 /** 1213 * Returns the size of the visible part of the view in view coordinates. 1214 * 1215 * @return a <code>Dimension</code> object giving the size of the view 1216 */ 1217 @Transient 1218 public Dimension getExtentSize() { 1219 return getSize(); 1220 } 1221 1222 1223 /** 1224 * Converts a size in pixel coordinates to view coordinates. 1225 * Subclasses of viewport that support "logical coordinates" 1226 * will override this method. 1227 * 1228 * @param size a <code>Dimension</code> object using pixel coordinates 1229 * @return a <code>Dimension</code> object converted to view coordinates 1230 */ 1231 public Dimension toViewCoordinates(Dimension size) { 1232 return new Dimension(size); 1233 } 1234 1235 /** 1236 * Converts a point in pixel coordinates to view coordinates. 1237 * Subclasses of viewport that support "logical coordinates" 1238 * will override this method. 1239 * 1240 * @param p a <code>Point</code> object using pixel coordinates 1241 * @return a <code>Point</code> object converted to view coordinates 1242 */ 1243 public Point toViewCoordinates(Point p) { 1244 return new Point(p); 1245 } 1246 1247 1248 /** 1249 * Sets the size of the visible part of the view using view coordinates. 1250 * 1251 * @param newExtent a <code>Dimension</code> object specifying 1252 * the size of the view 1253 */ 1254 public void setExtentSize(Dimension newExtent) { 1255 Dimension oldExtent = getExtentSize(); 1256 if (!newExtent.equals(oldExtent)) { 1257 setSize(newExtent); 1258 fireStateChanged(); 1259 } 1260 } 1261 1262 /** 1263 * A listener for the view. 1264 * <p> 1265 * <strong>Warning:</strong> 1266 * Serialized objects of this class will not be compatible with 1267 * future Swing releases. The current serialization support is 1268 * appropriate for short term storage or RMI between applications running 1269 * the same version of Swing. As of 1.4, support for long term storage 1270 * of all JavaBeans<sup><font size="-2">TM</font></sup> 1271 * has been added to the <code>java.beans</code> package. 1272 * Please see {@link java.beans.XMLEncoder}. 1273 */ 1274 protected class ViewListener extends ComponentAdapter implements Serializable 1275 { 1276 public void componentResized(ComponentEvent e) { 1277 fireStateChanged(); 1278 revalidate(); 1279 } 1280 } 1281 1282 /** 1283 * Creates a listener for the view. 1284 * @return a <code>ViewListener</code> 1285 */ 1286 protected ViewListener createViewListener() { 1287 return new ViewListener(); 1288 } 1289 1290 1291 /** 1292 * Subclassers can override this to install a different 1293 * layout manager (or <code>null</code>) in the constructor. Returns 1294 * the <code>LayoutManager</code> to install on the <code>JViewport</code>. 1295 * 1296 * @return a <code>LayoutManager</code> 1297 */ 1298 protected LayoutManager createLayoutManager() { 1299 return ViewportLayout.SHARED_INSTANCE; 1300 } 1301 1302 1303 /** 1304 * Adds a <code>ChangeListener</code> to the list that is 1305 * notified each time the view's 1306 * size, position, or the viewport's extent size has changed. 1307 * 1308 * @param l the <code>ChangeListener</code> to add 1309 * @see #removeChangeListener 1310 * @see #setViewPosition 1311 * @see #setViewSize 1312 * @see #setExtentSize 1313 */ 1314 public void addChangeListener(ChangeListener l) { 1315 listenerList.add(ChangeListener.class, l); 1316 } 1317 1318 /** 1319 * Removes a <code>ChangeListener</code> from the list that's notified each 1320 * time the views size, position, or the viewports extent size 1321 * has changed. 1322 * 1323 * @param l the <code>ChangeListener</code> to remove 1324 * @see #addChangeListener 1325 */ 1326 public void removeChangeListener(ChangeListener l) { 1327 listenerList.remove(ChangeListener.class, l); 1328 } 1329 1330 /** 1331 * Returns an array of all the <code>ChangeListener</code>s added 1332 * to this JViewport with addChangeListener(). 1333 * 1334 * @return all of the <code>ChangeListener</code>s added or an empty 1335 * array if no listeners have been added 1336 * @since 1.4 1337 */ 1338 public ChangeListener[] getChangeListeners() { 1339 return listenerList.getListeners(ChangeListener.class); 1340 } 1341 1342 /** 1343 * Notifies all <code>ChangeListeners</code> when the views 1344 * size, position, or the viewports extent size has changed. 1345 * 1346 * @see #addChangeListener 1347 * @see #removeChangeListener 1348 * @see EventListenerList 1349 */ 1350 protected void fireStateChanged() 1351 { 1352 Object[] listeners = listenerList.getListenerList(); 1353 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1354 if (listeners[i] == ChangeListener.class) { 1355 if (changeEvent == null) { 1356 changeEvent = new ChangeEvent(this); 1357 } 1358 ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent); 1359 } 1360 } 1361 } 1362 1363 /** 1364 * Always repaint in the parents coordinate system to make sure 1365 * only one paint is performed by the <code>RepaintManager</code>. 1366 * 1367 * @param tm maximum time in milliseconds before update 1368 * @param x the <code>x</code> coordinate (pixels over from left) 1369 * @param y the <code>y</code> coordinate (pixels down from top) 1370 * @param w the width 1371 * @param h the height 1372 * @see java.awt.Component#update(java.awt.Graphics) 1373 */ 1374 public void repaint(long tm, int x, int y, int w, int h) { 1375 Container parent = getParent(); 1376 if(parent != null) 1377 parent.repaint(tm,x+getX(),y+getY(),w,h); 1378 else 1379 super.repaint(tm,x,y,w,h); 1380 } 1381 1382 1383 /** 1384 * Returns a string representation of this <code>JViewport</code>. 1385 * This method 1386 * is intended to be used only for debugging purposes, and the 1387 * content and format of the returned string may vary between 1388 * implementations. The returned string may be empty but may not 1389 * be <code>null</code>. 1390 * 1391 * @return a string representation of this <code>JViewport</code> 1392 */ 1393 protected String paramString() { 1394 String isViewSizeSetString = (isViewSizeSet ? 1395 "true" : "false"); 1396 String lastPaintPositionString = (lastPaintPosition != null ? 1397 lastPaintPosition.toString() : ""); 1398 String scrollUnderwayString = (scrollUnderway ? 1399 "true" : "false"); 1400 1401 return super.paramString() + 1402 ",isViewSizeSet=" + isViewSizeSetString + 1403 ",lastPaintPosition=" + lastPaintPositionString + 1404 ",scrollUnderway=" + scrollUnderwayString; 1405 } 1406 1407 // 1408 // Following is used when doBlit is true. 1409 // 1410 1411 /** 1412 * Notifies listeners of a property change. This is subclassed to update 1413 * the <code>windowBlit</code> property. 1414 * (The <code>putClientProperty</code> property is final). 1415 * 1416 * @param propertyName a string containing the property name 1417 * @param oldValue the old value of the property 1418 * @param newValue the new value of the property 1419 */ 1420 protected void firePropertyChange(String propertyName, Object oldValue, 1421 Object newValue) { 1422 super.firePropertyChange(propertyName, oldValue, newValue); 1423 if (propertyName.equals(EnableWindowBlit)) { 1424 if (newValue != null) { 1425 setScrollMode(BLIT_SCROLL_MODE); 1426 } else { 1427 setScrollMode(SIMPLE_SCROLL_MODE); 1428 } 1429 } 1430 } 1431 1432 /** 1433 * Returns true if the component needs to be completely repainted after 1434 * a blit and a paint is received. 1435 */ 1436 private boolean needsRepaintAfterBlit() { 1437 // Find the first heavy weight ancestor. isObscured and 1438 // canDetermineObscurity are only appropriate for heavy weights. 1439 Component heavyParent = getParent(); 1440 1441 while (heavyParent != null && heavyParent.isLightweight()) { 1442 heavyParent = heavyParent.getParent(); 1443 } 1444 1445 if (heavyParent != null) { 1446 ComponentPeer peer = heavyParent.getPeer(); 1447 1448 if (peer != null && peer.canDetermineObscurity() && 1449 !peer.isObscured()) { 1450 // The peer says we aren't obscured, therefore we can assume 1451 // that we won't later be messaged to paint a portion that 1452 // we tried to blit that wasn't valid. 1453 // It is certainly possible that when we blited we were 1454 // obscured, and by the time this is invoked we aren't, but the 1455 // chances of that happening are pretty slim. 1456 return false; 1457 } 1458 } 1459 return true; 1460 } 1461 1462 private Timer createRepaintTimer() { 1463 Timer timer = new Timer(300, new ActionListener() { 1464 public void actionPerformed(ActionEvent ae) { 1465 // waitingForRepaint will be false if a paint came down 1466 // with the complete clip rect, in which case we don't 1467 // have to cause a repaint. 1468 if (waitingForRepaint) { 1469 repaint(); 1470 } 1471 } 1472 }); 1473 timer.setRepeats(false); 1474 return timer; 1475 } 1476 1477 /** 1478 * If the repaint manager has a dirty region for the view, the view is 1479 * asked to paint. 1480 * 1481 * @param g the <code>Graphics</code> context within which to paint 1482 */ 1483 private void flushViewDirtyRegion(Graphics g, Rectangle dirty) { 1484 JComponent view = (JComponent) getView(); 1485 if(dirty != null && dirty.width > 0 && dirty.height > 0) { 1486 dirty.x += view.getX(); 1487 dirty.y += view.getY(); 1488 Rectangle clip = g.getClipBounds(); 1489 if (clip == null) { 1490 // Only happens in 1.2 1491 g.setClip(0, 0, getWidth(), getHeight()); 1492 } 1493 g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height); 1494 clip = g.getClipBounds(); 1495 // Only paint the dirty region if it is visible. 1496 if (clip.width > 0 && clip.height > 0) { 1497 paintView(g); 1498 } 1499 } 1500 } 1501 1502 /** 1503 * Used when blitting. 1504 * 1505 * @param g the <code>Graphics</code> context within which to paint 1506 * @return true if blitting succeeded; otherwise false 1507 */ 1508 private boolean windowBlitPaint(Graphics g) { 1509 int width = getWidth(); 1510 int height = getHeight(); 1511 1512 if ((width == 0) || (height == 0)) { 1513 return false; 1514 } 1515 1516 boolean retValue; 1517 RepaintManager rm = RepaintManager.currentManager(this); 1518 JComponent view = (JComponent) getView(); 1519 1520 if (lastPaintPosition == null || 1521 lastPaintPosition.equals(getViewLocation())) { 1522 paintView(g); 1523 retValue = false; 1524 } else { 1525 // The image was scrolled. Manipulate the backing store and flush 1526 // it to g. 1527 Point blitFrom = new Point(); 1528 Point blitTo = new Point(); 1529 Dimension blitSize = new Dimension(); 1530 Rectangle blitPaint = new Rectangle(); 1531 1532 Point newLocation = getViewLocation(); 1533 int dx = newLocation.x - lastPaintPosition.x; 1534 int dy = newLocation.y - lastPaintPosition.y; 1535 boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, 1536 blitPaint); 1537 if (!canBlit) { 1538 paintView(g); 1539 retValue = false; 1540 } else { 1541 // Prepare the rest of the view; the part that has just been 1542 // exposed. 1543 Rectangle r = view.getBounds().intersection(blitPaint); 1544 r.x -= view.getX(); 1545 r.y -= view.getY(); 1546 1547 blitDoubleBuffered(view, g, r.x, r.y, r.width, r.height, 1548 blitFrom.x, blitFrom.y, blitTo.x, blitTo.y, 1549 blitSize.width, blitSize.height); 1550 retValue = true; 1551 } 1552 } 1553 lastPaintPosition = getViewLocation(); 1554 return retValue; 1555 } 1556 1557 // 1558 // NOTE: the code below uses paintForceDoubleBuffered for historical 1559 // reasons. If we're going to allow a blit we've already accounted for 1560 // everything that paintImmediately and _paintImmediately does, for that 1561 // reason we call into paintForceDoubleBuffered to diregard whether or 1562 // not setDoubleBuffered(true) was invoked on the view. 1563 // 1564 1565 private void blitDoubleBuffered(JComponent view, Graphics g, 1566 int clipX, int clipY, int clipW, int clipH, 1567 int blitFromX, int blitFromY, int blitToX, int blitToY, 1568 int blitW, int blitH) { 1569 // NOTE: 1570 // blitFrom/blitTo are in JViewport coordinates system 1571 // not the views coordinate space. 1572 // clip* are in the views coordinate space. 1573 RepaintManager rm = RepaintManager.currentManager(this); 1574 int bdx = blitToX - blitFromX; 1575 int bdy = blitToY - blitFromY; 1576 1577 // Shift the scrolled region 1578 rm.copyArea(this, g, blitFromX, blitFromY, blitW, blitH, bdx, bdy, 1579 false); 1580 1581 // Paint the newly exposed region. 1582 int x = view.getX(); 1583 int y = view.getY(); 1584 g.translate(x, y); 1585 g.setClip(clipX, clipY, clipW, clipH); 1586 view.paintForceDoubleBuffered(g); 1587 g.translate(-x, -y); 1588 } 1589 1590 /** 1591 * Called to paint the view, usually when <code>blitPaint</code> 1592 * can not blit. 1593 * 1594 * @param g the <code>Graphics</code> context within which to paint 1595 */ 1596 private void paintView(Graphics g) { 1597 Rectangle clip = g.getClipBounds(); 1598 JComponent view = (JComponent)getView(); 1599 1600 if (view.getWidth() >= getWidth()) { 1601 // Graphics is relative to JViewport, need to map to view's 1602 // coordinates space. 1603 int x = view.getX(); 1604 int y = view.getY(); 1605 g.translate(x, y); 1606 g.setClip(clip.x - x, clip.y - y, clip.width, clip.height); 1607 view.paintForceDoubleBuffered(g); 1608 g.translate(-x, -y); 1609 g.setClip(clip.x, clip.y, clip.width, clip.height); 1610 } 1611 else { 1612 // To avoid any problems that may result from the viewport being 1613 // bigger than the view we start painting from the viewport. 1614 try { 1615 inBlitPaint = true; 1616 paintForceDoubleBuffered(g); 1617 } finally { 1618 inBlitPaint = false; 1619 } 1620 } 1621 } 1622 1623 /** 1624 * Returns true if the viewport is not obscured by one of its ancestors, 1625 * or its ancestors children and if the viewport is showing. Blitting 1626 * when the view isn't showing will work, 1627 * or rather <code>copyArea</code> will work, 1628 * but will not produce the expected behavior. 1629 */ 1630 private boolean canUseWindowBlitter() { 1631 if (!isShowing() || (!(getParent() instanceof JComponent) && 1632 !(getView() instanceof JComponent))) { 1633 return false; 1634 } 1635 if (isPainting()) { 1636 // We're in the process of painting, don't blit. If we were 1637 // to blit we would draw on top of what we're already drawing, 1638 // so bail. 1639 return false; 1640 } 1641 1642 Rectangle dirtyRegion = RepaintManager.currentManager(this). 1643 getDirtyRegion((JComponent)getParent()); 1644 1645 if (dirtyRegion != null && dirtyRegion.width > 0 && 1646 dirtyRegion.height > 0) { 1647 // Part of the scrollpane needs to be repainted too, don't blit. 1648 return false; 1649 } 1650 1651 Rectangle clip = new Rectangle(0,0,getWidth(),getHeight()); 1652 Rectangle oldClip = new Rectangle(); 1653 Rectangle tmp2 = null; 1654 Container parent; 1655 Component lastParent = null; 1656 int x, y, w, h; 1657 1658 for(parent = this; parent != null && isLightweightComponent(parent); parent = parent.getParent()) { 1659 x = parent.getX(); 1660 y = parent.getY(); 1661 w = parent.getWidth(); 1662 h = parent.getHeight(); 1663 1664 oldClip.setBounds(clip); 1665 SwingUtilities.computeIntersection(0, 0, w, h, clip); 1666 if(!clip.equals(oldClip)) 1667 return false; 1668 1669 if(lastParent != null && parent instanceof JComponent && 1670 !((JComponent)parent).isOptimizedDrawingEnabled()) { 1671 Component comps[] = parent.getComponents(); 1672 int index = 0; 1673 1674 for(int i = comps.length - 1 ;i >= 0; i--) { 1675 if(comps[i] == lastParent) { 1676 index = i - 1; 1677 break; 1678 } 1679 } 1680 1681 while(index >= 0) { 1682 tmp2 = comps[index].getBounds(tmp2); 1683 1684 if(tmp2.intersects(clip)) 1685 return false; 1686 index--; 1687 } 1688 } 1689 clip.x += x; 1690 clip.y += y; 1691 lastParent = parent; 1692 } 1693 if (parent == null) { 1694 // No Window parent. 1695 return false; 1696 } 1697 return true; 1698 } 1699 1700 1701 ///////////////// 1702 // Accessibility support 1703 //////////////// 1704 1705 /** 1706 * Gets the AccessibleContext associated with this JViewport. 1707 * For viewports, the AccessibleContext takes the form of an 1708 * AccessibleJViewport. 1709 * A new AccessibleJViewport instance is created if necessary. 1710 * 1711 * @return an AccessibleJViewport that serves as the 1712 * AccessibleContext of this JViewport 1713 */ 1714 public AccessibleContext getAccessibleContext() { 1715 if (accessibleContext == null) { 1716 accessibleContext = new AccessibleJViewport(); 1717 } 1718 return accessibleContext; 1719 } 1720 1721 /** 1722 * This class implements accessibility support for the 1723 * <code>JViewport</code> class. It provides an implementation of the 1724 * Java Accessibility API appropriate to viewport user-interface elements. 1725 * <p> 1726 * <strong>Warning:</strong> 1727 * Serialized objects of this class will not be compatible with 1728 * future Swing releases. The current serialization support is 1729 * appropriate for short term storage or RMI between applications running 1730 * the same version of Swing. As of 1.4, support for long term storage 1731 * of all JavaBeans<sup><font size="-2">TM</font></sup> 1732 * has been added to the <code>java.beans</code> package. 1733 * Please see {@link java.beans.XMLEncoder}. 1734 */ 1735 protected class AccessibleJViewport extends AccessibleJComponent { 1736 /** 1737 * Get the role of this object. 1738 * 1739 * @return an instance of AccessibleRole describing the role of 1740 * the object 1741 */ 1742 public AccessibleRole getAccessibleRole() { 1743 return AccessibleRole.VIEWPORT; 1744 } 1745 } // inner class AccessibleJViewport 1746 }