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