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