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