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