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