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