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