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