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