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