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