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