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