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