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     public void reshape(int x, int y, int w, int h) {
 833         boolean sizeChanged = (getWidth() != w) || (getHeight() != h);
 834         if (sizeChanged) {
 835             backingStoreImage = null;
 836         }
 837         super.reshape(x, y, w, h);
 838         if (sizeChanged || viewChanged) {
 839             viewChanged = false;
 840 
 841             fireStateChanged();
 842         }
 843     }
 844 
 845 
 846     /**
 847       * Used to control the method of scrolling the viewport contents.
 848       * You may want to change this mode to get maximum performance for your
 849       * use case.
 850       *
 851       * @param mode one of the following values:
 852       * <ul>
 853       * <li> JViewport.BLIT_SCROLL_MODE
 854       * <li> JViewport.BACKINGSTORE_SCROLL_MODE
 855       * <li> JViewport.SIMPLE_SCROLL_MODE
 856       * </ul>
 857       *
 858       * @see #BLIT_SCROLL_MODE
 859       * @see #BACKINGSTORE_SCROLL_MODE
 860       * @see #SIMPLE_SCROLL_MODE
 861       *
 862       * @beaninfo
 863       *        bound: false
 864       *  description: Method of moving contents for incremental scrolls.
 865       *         enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE
 866       *               BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE
 867       *               SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE
 868       *
 869       * @since 1.3
 870       */
 871     public void setScrollMode(int mode) {
 872         scrollMode = mode;
 873         backingStore = mode == BACKINGSTORE_SCROLL_MODE;
 874     }
 875 
 876     /**
 877       * Returns the current scrolling mode.
 878       *
 879       * @return the <code>scrollMode</code> property
 880       * @see #setScrollMode
 881       * @since 1.3
 882       */
 883     public int getScrollMode() {
 884         return scrollMode;
 885     }
 886 
 887     /**
 888      * Returns <code>true</code> if this viewport is maintaining
 889      * an offscreen image of its contents.
 890      *
 891      * @return <code>true</code> if <code>scrollMode</code> is
 892      *    <code>BACKINGSTORE_SCROLL_MODE</code>
 893      *
 894      * @deprecated As of Java 2 platform v1.3, replaced by
 895      *             <code>getScrollMode()</code>.
 896      */
 897     @Deprecated
 898     public boolean isBackingStoreEnabled() {
 899         return scrollMode == BACKINGSTORE_SCROLL_MODE;
 900     }
 901 
 902 
 903     /**
 904      * If true if this viewport will maintain an offscreen
 905      * image of its contents.  The image is used to reduce the cost
 906      * of small one dimensional changes to the <code>viewPosition</code>.
 907      * Rather than repainting the entire viewport we use
 908      * <code>Graphics.copyArea</code> to effect some of the scroll.
 909      *
 910      * @param enabled if true, maintain an offscreen backing store
 911      *
 912      * @deprecated As of Java 2 platform v1.3, replaced by
 913      *             <code>setScrollMode()</code>.
 914      */
 915     @Deprecated
 916     public void setBackingStoreEnabled(boolean enabled) {
 917         if (enabled) {
 918             setScrollMode(BACKINGSTORE_SCROLL_MODE);
 919         } else {
 920             setScrollMode(BLIT_SCROLL_MODE);
 921         }
 922     }
 923 
 924     private boolean isBlitting() {
 925         Component view = getView();
 926         return (scrollMode == BLIT_SCROLL_MODE) &&
 927                (view instanceof JComponent) && view.isOpaque();
 928     }
 929 
 930 
 931     /**
 932      * Returns the <code>JViewport</code>'s one child or <code>null</code>.
 933      *
 934      * @return the viewports child, or <code>null</code> if none exists
 935      *
 936      * @see #setView
 937      */
 938     public Component getView() {
 939         return (getComponentCount() > 0) ? getComponent(0) : null;
 940     }
 941 
 942     /**
 943      * Sets the <code>JViewport</code>'s one lightweight child
 944      * (<code>view</code>), which can be <code>null</code>.
 945      *
 946      * @param view the viewport's new lightweight child
 947      *
 948      * @see #getView
 949      */
 950     public void setView(Component view) {
 951 
 952         /* Remove the viewport's existing children, if any.
 953          * Note that removeAll() isn't used here because it
 954          * doesn't call remove() (which JViewport overrides).
 955          */
 956         int n = getComponentCount();
 957         for(int i = n - 1; i >= 0; i--) {
 958             remove(getComponent(i));
 959         }
 960 
 961         isViewSizeSet = false;
 962 
 963         if (view != null) {
 964             super.addImpl(view, null, -1);
 965             viewListener = createViewListener();
 966             view.addComponentListener(viewListener);
 967         }
 968 
 969         if (hasHadValidView) {
 970             // Only fire a change if a view has been installed.
 971             fireStateChanged();
 972         }
 973         else if (view != null) {
 974             hasHadValidView = true;
 975         }
 976 
 977         viewChanged = true;
 978 
 979         revalidate();
 980         repaint();
 981     }
 982 
 983 
 984     /**
 985      * If the view's size hasn't been explicitly set, return the
 986      * preferred size, otherwise return the view's current size.
 987      * If there is no view, return 0,0.
 988      *
 989      * @return a <code>Dimension</code> object specifying the size of the view
 990      */
 991     public Dimension getViewSize() {
 992         Component view = getView();
 993 
 994         if (view == null) {
 995             return new Dimension(0,0);
 996         }
 997         else if (isViewSizeSet) {
 998             return view.getSize();
 999         }
1000         else {
1001             return view.getPreferredSize();
1002         }
1003     }
1004 
1005 
1006     /**
1007      * Sets the size of the view.  A state changed event will be fired.
1008      *
1009      * @param newSize a <code>Dimension</code> object specifying the new
1010      *          size of the view
1011      */
1012     public void setViewSize(Dimension newSize) {
1013         Component view = getView();
1014         if (view != null) {
1015             Dimension oldSize = view.getSize();
1016             if (!newSize.equals(oldSize)) {
1017                 // scrollUnderway will be true if this is invoked as the
1018                 // result of a validate and setViewPosition was previously
1019                 // invoked.
1020                 scrollUnderway = false;
1021                 view.setSize(newSize);
1022                 isViewSizeSet = true;
1023                 fireStateChanged();
1024             }
1025         }
1026     }
1027 
1028     /**
1029      * Returns the view coordinates that appear in the upper left
1030      * hand corner of the viewport, or 0,0 if there's no view.
1031      *
1032      * @return a <code>Point</code> object giving the upper left coordinates
1033      */
1034     public Point getViewPosition() {
1035         Component view = getView();
1036         if (view != null) {
1037             Point p = view.getLocation();
1038             p.x = -p.x;
1039             p.y = -p.y;
1040             return p;
1041         }
1042         else {
1043             return new Point(0,0);
1044         }
1045     }
1046 
1047 
1048     /**
1049      * Sets the view coordinates that appear in the upper left
1050      * hand corner of the viewport, does nothing if there's no view.
1051      *
1052      * @param p  a <code>Point</code> object giving the upper left coordinates
1053      */
1054     public void setViewPosition(Point p)
1055     {
1056         Component view = getView();
1057         if (view == null) {
1058             return;
1059         }
1060 
1061         int oldX, oldY, x = p.x, y = p.y;
1062 
1063         /* Collect the old x,y values for the views location
1064          * and do the song and dance to avoid allocating
1065          * a Rectangle object if we don't have to.
1066          */
1067         if (view instanceof JComponent) {
1068             JComponent c = (JComponent)view;
1069             oldX = c.getX();
1070             oldY = c.getY();
1071         }
1072         else {
1073             Rectangle r = view.getBounds();
1074             oldX = r.x;
1075             oldY = r.y;
1076         }
1077 
1078         /* The view scrolls in the opposite direction to mouse
1079          * movement.
1080          */
1081         int newX = -x;
1082         int newY = -y;
1083 
1084         if ((oldX != newX) || (oldY != newY)) {
1085             if (!waitingForRepaint && isBlitting() && canUseWindowBlitter()) {
1086                 RepaintManager rm = RepaintManager.currentManager(this);
1087                 // The cast to JComponent will work, if view is not
1088                 // a JComponent, isBlitting will return false.
1089                 JComponent jview = (JComponent)view;
1090                 Rectangle dirty = rm.getDirtyRegion(jview);
1091                 if (dirty == null || !dirty.contains(jview.getVisibleRect())) {
1092                     rm.beginPaint();
1093                     try {
1094                         Graphics g = JComponent.safelyGetGraphics(this);
1095                         flushViewDirtyRegion(g, dirty);
1096                         view.setLocation(newX, newY);
1097                         Rectangle r = new Rectangle(
1098                             0, 0, getWidth(), Math.min(getHeight(), jview.getHeight()));
1099                         g.setClip(r);
1100                         // Repaint the complete component if the blit succeeded
1101                         // and needsRepaintAfterBlit returns true.
1102                         repaintAll = (windowBlitPaint(g) &&
1103                                       needsRepaintAfterBlit());
1104                         g.dispose();
1105                         rm.notifyRepaintPerformed(this, r.x, r.y, r.width, r.height);
1106                         rm.markCompletelyClean((JComponent)getParent());
1107                         rm.markCompletelyClean(this);
1108                         rm.markCompletelyClean(jview);
1109                     } finally {
1110                         rm.endPaint();
1111                     }
1112                 }
1113                 else {
1114                     // The visible region is dirty, no point in doing copyArea
1115                     view.setLocation(newX, newY);
1116                     repaintAll = false;
1117                 }
1118             }
1119             else {
1120                 scrollUnderway = true;
1121                 // This calls setBounds(), and then repaint().
1122                 view.setLocation(newX, newY);
1123                 repaintAll = false;
1124             }
1125             // we must validate the hierarchy to not break the hw/lw mixing
1126             revalidate();
1127             fireStateChanged();
1128         }
1129     }
1130 
1131 
1132     /**
1133      * Returns a rectangle whose origin is <code>getViewPosition</code>
1134      * and size is <code>getExtentSize</code>.
1135      * This is the visible part of the view, in view coordinates.
1136      *
1137      * @return a <code>Rectangle</code> giving the visible part of
1138      *          the view using view coordinates.
1139      */
1140     public Rectangle getViewRect() {
1141         return new Rectangle(getViewPosition(), getExtentSize());
1142     }
1143 
1144 
1145     /**
1146      * Computes the parameters for a blit where the backing store image
1147      * currently contains <code>oldLoc</code> in the upper left hand corner
1148      * and we're scrolling to <code>newLoc</code>.
1149      * The parameters are modified
1150      * to return the values required for the blit.
1151      *
1152      * @param dx  the horizontal delta
1153      * @param dy  the vertical delta
1154      * @param blitFrom the <code>Point</code> we're blitting from
1155      * @param blitTo the <code>Point</code> we're blitting to
1156      * @param blitSize the <code>Dimension</code> of the area to blit
1157      * @param blitPaint the area to blit
1158      * @return  true if the parameters are modified and we're ready to blit;
1159      *          false otherwise
1160      */
1161     protected boolean computeBlit(
1162         int dx,
1163         int dy,
1164         Point blitFrom,
1165         Point blitTo,
1166         Dimension blitSize,
1167         Rectangle blitPaint)
1168     {
1169         int dxAbs = Math.abs(dx);
1170         int dyAbs = Math.abs(dy);
1171         Dimension extentSize = getExtentSize();
1172 
1173         if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) {
1174             if (dy < 0) {
1175                 blitFrom.y = -dy;
1176                 blitTo.y = 0;
1177                 blitPaint.y = extentSize.height + dy;
1178             }
1179             else {
1180                 blitFrom.y = 0;
1181                 blitTo.y = dy;
1182                 blitPaint.y = 0;
1183             }
1184 
1185             blitPaint.x = blitFrom.x = blitTo.x = 0;
1186 
1187             blitSize.width = extentSize.width;
1188             blitSize.height = extentSize.height - dyAbs;
1189 
1190             blitPaint.width = extentSize.width;
1191             blitPaint.height = dyAbs;
1192 
1193             return true;
1194         }
1195 
1196         else if ((dy == 0) && (dx != 0) && (dxAbs < extentSize.width)) {
1197             if (dx < 0) {
1198                 blitFrom.x = -dx;
1199                 blitTo.x = 0;
1200                 blitPaint.x = extentSize.width + dx;
1201             }
1202             else {
1203                 blitFrom.x = 0;
1204                 blitTo.x = dx;
1205                 blitPaint.x = 0;
1206             }
1207 
1208             blitPaint.y = blitFrom.y = blitTo.y = 0;
1209 
1210             blitSize.width = extentSize.width - dxAbs;
1211             blitSize.height = extentSize.height;
1212 
1213             blitPaint.width = dxAbs;
1214             blitPaint.height = extentSize.height;
1215 
1216             return true;
1217         }
1218 
1219         else {
1220             return false;
1221         }
1222     }
1223 
1224 
1225     /**
1226      * Returns the size of the visible part of the view in view coordinates.
1227      *
1228      * @return a <code>Dimension</code> object giving the size of the view
1229      */
1230     @Transient
1231     public Dimension getExtentSize() {
1232         return getSize();
1233     }
1234 
1235 
1236     /**
1237      * Converts a size in pixel coordinates to view coordinates.
1238      * Subclasses of viewport that support "logical coordinates"
1239      * will override this method.
1240      *
1241      * @param size  a <code>Dimension</code> object using pixel coordinates
1242      * @return a <code>Dimension</code> object converted to view coordinates
1243      */
1244     public Dimension toViewCoordinates(Dimension size) {
1245         return new Dimension(size);
1246     }
1247 
1248     /**
1249      * Converts a point in pixel coordinates to view coordinates.
1250      * Subclasses of viewport that support "logical coordinates"
1251      * will override this method.
1252      *
1253      * @param p  a <code>Point</code> object using pixel coordinates
1254      * @return a <code>Point</code> object converted to view coordinates
1255      */
1256     public Point toViewCoordinates(Point p) {
1257         return new Point(p);
1258     }
1259 
1260 
1261     /**
1262      * Sets the size of the visible part of the view using view coordinates.
1263      *
1264      * @param newExtent  a <code>Dimension</code> object specifying
1265      *          the size of the view
1266      */
1267     public void setExtentSize(Dimension newExtent) {
1268         Dimension oldExtent = getExtentSize();
1269         if (!newExtent.equals(oldExtent)) {
1270             setSize(newExtent);
1271             fireStateChanged();
1272         }
1273     }
1274 
1275     /**
1276      * A listener for the view.
1277      * <p>
1278      * <strong>Warning:</strong>
1279      * Serialized objects of this class will not be compatible with
1280      * future Swing releases. The current serialization support is
1281      * appropriate for short term storage or RMI between applications running
1282      * the same version of Swing.  As of 1.4, support for long term storage
1283      * of all JavaBeans&trade;
1284      * has been added to the <code>java.beans</code> package.
1285      * Please see {@link java.beans.XMLEncoder}.
1286      */
1287     @SuppressWarnings("serial") // Same-version serialization only
1288     protected class ViewListener extends ComponentAdapter implements Serializable
1289     {
1290         public void componentResized(ComponentEvent e) {
1291             fireStateChanged();
1292             revalidate();
1293         }
1294     }
1295 
1296     /**
1297      * Creates a listener for the view.
1298      * @return a <code>ViewListener</code>
1299      */
1300     protected ViewListener createViewListener() {
1301         return new ViewListener();
1302     }
1303 
1304 
1305     /**
1306      * Subclassers can override this to install a different
1307      * layout manager (or <code>null</code>) in the constructor.  Returns
1308      * the <code>LayoutManager</code> to install on the <code>JViewport</code>.
1309      *
1310      * @return a <code>LayoutManager</code>
1311      */
1312     protected LayoutManager createLayoutManager() {
1313         return ViewportLayout.SHARED_INSTANCE;
1314     }
1315 
1316 
1317     /**
1318      * Adds a <code>ChangeListener</code> to the list that is
1319      * notified each time the view's
1320      * size, position, or the viewport's extent size has changed.
1321      *
1322      * @param l the <code>ChangeListener</code> to add
1323      * @see #removeChangeListener
1324      * @see #setViewPosition
1325      * @see #setViewSize
1326      * @see #setExtentSize
1327      */
1328     public void addChangeListener(ChangeListener l) {
1329         listenerList.add(ChangeListener.class, l);
1330     }
1331 
1332     /**
1333      * Removes a <code>ChangeListener</code> from the list that's notified each
1334      * time the views size, position, or the viewports extent size
1335      * has changed.
1336      *
1337      * @param l the <code>ChangeListener</code> to remove
1338      * @see #addChangeListener
1339      */
1340     public void removeChangeListener(ChangeListener l) {
1341         listenerList.remove(ChangeListener.class, l);
1342     }
1343 
1344     /**
1345      * Returns an array of all the <code>ChangeListener</code>s added
1346      * to this JViewport with addChangeListener().
1347      *
1348      * @return all of the <code>ChangeListener</code>s added or an empty
1349      *         array if no listeners have been added
1350      * @since 1.4
1351      */
1352     public ChangeListener[] getChangeListeners() {
1353         return listenerList.getListeners(ChangeListener.class);
1354     }
1355 
1356     /**
1357      * Notifies all <code>ChangeListeners</code> when the views
1358      * size, position, or the viewports extent size has changed.
1359      *
1360      * @see #addChangeListener
1361      * @see #removeChangeListener
1362      * @see EventListenerList
1363      */
1364     protected void fireStateChanged()
1365     {
1366         Object[] listeners = listenerList.getListenerList();
1367         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1368             if (listeners[i] == ChangeListener.class) {
1369                 if (changeEvent == null) {
1370                     changeEvent = new ChangeEvent(this);
1371                 }
1372                 ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
1373             }
1374         }
1375     }
1376 
1377     /**
1378      * Always repaint in the parents coordinate system to make sure
1379      * only one paint is performed by the <code>RepaintManager</code>.
1380      *
1381      * @param     tm   maximum time in milliseconds before update
1382      * @param     x    the <code>x</code> coordinate (pixels over from left)
1383      * @param     y    the <code>y</code> coordinate (pixels down from top)
1384      * @param     w    the width
1385      * @param     h   the height
1386      * @see       java.awt.Component#update(java.awt.Graphics)
1387      */
1388     public void repaint(long tm, int x, int y, int w, int h) {
1389         Container parent = getParent();
1390         if(parent != null)
1391             parent.repaint(tm,x+getX(),y+getY(),w,h);
1392         else
1393             super.repaint(tm,x,y,w,h);
1394     }
1395 
1396 
1397     /**
1398      * Returns a string representation of this <code>JViewport</code>.
1399      * This method
1400      * is intended to be used only for debugging purposes, and the
1401      * content and format of the returned string may vary between
1402      * implementations. The returned string may be empty but may not
1403      * be <code>null</code>.
1404      *
1405      * @return  a string representation of this <code>JViewport</code>
1406      */
1407     protected String paramString() {
1408         String isViewSizeSetString = (isViewSizeSet ?
1409                                       "true" : "false");
1410         String lastPaintPositionString = (lastPaintPosition != null ?
1411                                           lastPaintPosition.toString() : "");
1412         String scrollUnderwayString = (scrollUnderway ?
1413                                        "true" : "false");
1414 
1415         return super.paramString() +
1416         ",isViewSizeSet=" + isViewSizeSetString +
1417         ",lastPaintPosition=" + lastPaintPositionString +
1418         ",scrollUnderway=" + scrollUnderwayString;
1419     }
1420 
1421     //
1422     // Following is used when doBlit is true.
1423     //
1424 
1425     /**
1426      * Notifies listeners of a property change. This is subclassed to update
1427      * the <code>windowBlit</code> property.
1428      * (The <code>putClientProperty</code> property is final).
1429      *
1430      * @param propertyName a string containing the property name
1431      * @param oldValue the old value of the property
1432      * @param newValue  the new value of the property
1433      */
1434     protected void firePropertyChange(String propertyName, Object oldValue,
1435                                       Object newValue) {
1436         super.firePropertyChange(propertyName, oldValue, newValue);
1437         if (propertyName.equals(EnableWindowBlit)) {
1438             if (newValue != null) {
1439                 setScrollMode(BLIT_SCROLL_MODE);
1440             } else {
1441                 setScrollMode(SIMPLE_SCROLL_MODE);
1442             }
1443         }
1444     }
1445 
1446     /**
1447      * Returns true if the component needs to be completely repainted after
1448      * a blit and a paint is received.
1449      */
1450     private boolean needsRepaintAfterBlit() {
1451         // Find the first heavy weight ancestor. isObscured and
1452         // canDetermineObscurity are only appropriate for heavy weights.
1453         Component heavyParent = getParent();
1454 
1455         while (heavyParent != null && heavyParent.isLightweight()) {
1456             heavyParent = heavyParent.getParent();
1457         }
1458 
1459         if (heavyParent != null) {
1460             ComponentPeer peer = heavyParent.getPeer();
1461 
1462             if (peer != null && peer.canDetermineObscurity() &&
1463                                 !peer.isObscured()) {
1464                 // The peer says we aren't obscured, therefore we can assume
1465                 // that we won't later be messaged to paint a portion that
1466                 // we tried to blit that wasn't valid.
1467                 // It is certainly possible that when we blited we were
1468                 // obscured, and by the time this is invoked we aren't, but the
1469                 // chances of that happening are pretty slim.
1470                 return false;
1471             }
1472         }
1473         return true;
1474     }
1475 
1476     private Timer createRepaintTimer() {
1477         Timer timer = new Timer(300, new ActionListener() {
1478             public void actionPerformed(ActionEvent ae) {
1479                 // waitingForRepaint will be false if a paint came down
1480                 // with the complete clip rect, in which case we don't
1481                 // have to cause a repaint.
1482                 if (waitingForRepaint) {
1483                     repaint();
1484                 }
1485             }
1486         });
1487         timer.setRepeats(false);
1488         return timer;
1489     }
1490 
1491     /**
1492      * If the repaint manager has a dirty region for the view, the view is
1493      * asked to paint.
1494      *
1495      * @param g  the <code>Graphics</code> context within which to paint
1496      */
1497     private void flushViewDirtyRegion(Graphics g, Rectangle dirty) {
1498         JComponent view = (JComponent) getView();
1499         if(dirty != null && dirty.width > 0 && dirty.height > 0) {
1500             dirty.x += view.getX();
1501             dirty.y += view.getY();
1502             Rectangle clip = g.getClipBounds();
1503             if (clip == null) {
1504                 // Only happens in 1.2
1505                 g.setClip(0, 0, getWidth(), getHeight());
1506             }
1507             g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height);
1508             clip = g.getClipBounds();
1509             // Only paint the dirty region if it is visible.
1510             if (clip.width > 0 && clip.height > 0) {
1511                 paintView(g);
1512             }
1513         }
1514     }
1515 
1516     /**
1517      * Used when blitting.
1518      *
1519      * @param g  the <code>Graphics</code> context within which to paint
1520      * @return true if blitting succeeded; otherwise false
1521      */
1522     private boolean windowBlitPaint(Graphics g) {
1523         int width = getWidth();
1524         int height = getHeight();
1525 
1526         if ((width == 0) || (height == 0)) {
1527             return false;
1528         }
1529 
1530         boolean retValue;
1531         RepaintManager rm = RepaintManager.currentManager(this);
1532         JComponent view = (JComponent) getView();
1533 
1534         if (lastPaintPosition == null ||
1535             lastPaintPosition.equals(getViewLocation())) {
1536             paintView(g);
1537             retValue = false;
1538         } else {
1539             // The image was scrolled. Manipulate the backing store and flush
1540             // it to g.
1541             Point blitFrom = new Point();
1542             Point blitTo = new Point();
1543             Dimension blitSize = new Dimension();
1544             Rectangle blitPaint = new Rectangle();
1545 
1546             Point newLocation = getViewLocation();
1547             int dx = newLocation.x - lastPaintPosition.x;
1548             int dy = newLocation.y - lastPaintPosition.y;
1549             boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize,
1550                                           blitPaint);
1551             if (!canBlit) {
1552                 paintView(g);
1553                 retValue = false;
1554             } else {
1555                 // Prepare the rest of the view; the part that has just been
1556                 // exposed.
1557                 Rectangle r = view.getBounds().intersection(blitPaint);
1558                 r.x -= view.getX();
1559                 r.y -= view.getY();
1560 
1561                 blitDoubleBuffered(view, g, r.x, r.y, r.width, r.height,
1562                                    blitFrom.x, blitFrom.y, blitTo.x, blitTo.y,
1563                                    blitSize.width, blitSize.height);
1564                 retValue = true;
1565             }
1566         }
1567         lastPaintPosition = getViewLocation();
1568         return retValue;
1569     }
1570 
1571     //
1572     // NOTE: the code below uses paintForceDoubleBuffered for historical
1573     // reasons.  If we're going to allow a blit we've already accounted for
1574     // everything that paintImmediately and _paintImmediately does, for that
1575     // reason we call into paintForceDoubleBuffered to diregard whether or
1576     // not setDoubleBuffered(true) was invoked on the view.
1577     //
1578 
1579     private void blitDoubleBuffered(JComponent view, Graphics g,
1580                                     int clipX, int clipY, int clipW, int clipH,
1581                                     int blitFromX, int blitFromY, int blitToX, int blitToY,
1582                                     int blitW, int blitH) {
1583         // NOTE:
1584         //   blitFrom/blitTo are in JViewport coordinates system
1585         //     not the views coordinate space.
1586         //   clip* are in the views coordinate space.
1587         RepaintManager rm = RepaintManager.currentManager(this);
1588         int bdx = blitToX - blitFromX;
1589         int bdy = blitToY - blitFromY;
1590 
1591         Composite oldComposite = null;
1592         // Shift the scrolled region
1593         if (g instanceof Graphics2D) {
1594             Graphics2D g2d = (Graphics2D) g;
1595             oldComposite = g2d.getComposite();
1596             g2d.setComposite(AlphaComposite.Src);
1597         }
1598         rm.copyArea(this, g, blitFromX, blitFromY, blitW, blitH, bdx, bdy,
1599                     false);
1600         if (oldComposite != null) {
1601             ((Graphics2D) g).setComposite(oldComposite);
1602         }
1603         // Paint the newly exposed region.
1604         int x = view.getX();
1605         int y = view.getY();
1606         g.translate(x, y);
1607         g.setClip(clipX, clipY, clipW, clipH);
1608         view.paintForceDoubleBuffered(g);
1609         g.translate(-x, -y);
1610     }
1611 
1612     /**
1613      * Called to paint the view, usually when <code>blitPaint</code>
1614      * can not blit.
1615      *
1616      * @param g the <code>Graphics</code> context within which to paint
1617      */
1618     private void paintView(Graphics g) {
1619         Rectangle clip = g.getClipBounds();
1620         JComponent view = (JComponent)getView();
1621 
1622         if (view.getWidth() >= getWidth()) {
1623             // Graphics is relative to JViewport, need to map to view's
1624             // coordinates space.
1625             int x = view.getX();
1626             int y = view.getY();
1627             g.translate(x, y);
1628             g.setClip(clip.x - x, clip.y - y, clip.width, clip.height);
1629             view.paintForceDoubleBuffered(g);
1630             g.translate(-x, -y);
1631             g.setClip(clip.x, clip.y, clip.width, clip.height);
1632         }
1633         else {
1634             // To avoid any problems that may result from the viewport being
1635             // bigger than the view we start painting from the viewport.
1636             try {
1637                 inBlitPaint = true;
1638                 paintForceDoubleBuffered(g);
1639             } finally {
1640                 inBlitPaint = false;
1641             }
1642         }
1643     }
1644 
1645     /**
1646      * Returns true if the viewport is not obscured by one of its ancestors,
1647      * or its ancestors children and if the viewport is showing. Blitting
1648      * when the view isn't showing will work,
1649      * or rather <code>copyArea</code> will work,
1650      * but will not produce the expected behavior.
1651      */
1652     private boolean canUseWindowBlitter() {
1653         if (!isShowing() || (!(getParent() instanceof JComponent) &&
1654                              !(getView() instanceof JComponent))) {
1655             return false;
1656         }
1657         if (isPainting()) {
1658             // We're in the process of painting, don't blit. If we were
1659             // to blit we would draw on top of what we're already drawing,
1660             // so bail.
1661             return false;
1662         }
1663 
1664         Rectangle dirtyRegion = RepaintManager.currentManager(this).
1665                                 getDirtyRegion((JComponent)getParent());
1666 
1667         if (dirtyRegion != null && dirtyRegion.width > 0 &&
1668             dirtyRegion.height > 0) {
1669             // Part of the scrollpane needs to be repainted too, don't blit.
1670             return false;
1671         }
1672 
1673         Rectangle clip = new Rectangle(0,0,getWidth(),getHeight());
1674         Rectangle oldClip = new Rectangle();
1675         Rectangle tmp2 = null;
1676         Container parent;
1677         Component lastParent = null;
1678         int x, y, w, h;
1679 
1680         for(parent = this; parent != null && isLightweightComponent(parent); parent = parent.getParent()) {
1681             x = parent.getX();
1682             y = parent.getY();
1683             w = parent.getWidth();
1684             h = parent.getHeight();
1685 
1686             oldClip.setBounds(clip);
1687             SwingUtilities.computeIntersection(0, 0, w, h, clip);
1688             if(!clip.equals(oldClip))
1689                 return false;
1690 
1691             if(lastParent != null && parent instanceof JComponent &&
1692                !((JComponent)parent).isOptimizedDrawingEnabled()) {
1693                 Component comps[] = parent.getComponents();
1694                 int index = 0;
1695 
1696                 for(int i = comps.length - 1 ;i >= 0; i--) {
1697                     if(comps[i] == lastParent) {
1698                         index = i - 1;
1699                         break;
1700                     }
1701                 }
1702 
1703                 while(index >= 0) {
1704                     tmp2 = comps[index].getBounds(tmp2);
1705 
1706                     if(tmp2.intersects(clip))
1707                         return false;
1708                     index--;
1709                 }
1710             }
1711             clip.x += x;
1712             clip.y += y;
1713             lastParent = parent;
1714         }
1715         if (parent == null) {
1716             // No Window parent.
1717             return false;
1718         }
1719         return true;
1720     }
1721 
1722 
1723 /////////////////
1724 // Accessibility support
1725 ////////////////
1726 
1727     /**
1728      * Gets the AccessibleContext associated with this JViewport.
1729      * For viewports, the AccessibleContext takes the form of an
1730      * AccessibleJViewport.
1731      * A new AccessibleJViewport instance is created if necessary.
1732      *
1733      * @return an AccessibleJViewport that serves as the
1734      *         AccessibleContext of this JViewport
1735      */
1736     public AccessibleContext getAccessibleContext() {
1737         if (accessibleContext == null) {
1738             accessibleContext = new AccessibleJViewport();
1739         }
1740         return accessibleContext;
1741     }
1742 
1743     /**
1744      * This class implements accessibility support for the
1745      * <code>JViewport</code> class.  It provides an implementation of the
1746      * Java Accessibility API appropriate to viewport user-interface elements.
1747      * <p>
1748      * <strong>Warning:</strong>
1749      * Serialized objects of this class will not be compatible with
1750      * future Swing releases. The current serialization support is
1751      * appropriate for short term storage or RMI between applications running
1752      * the same version of Swing.  As of 1.4, support for long term storage
1753      * of all JavaBeans&trade;
1754      * has been added to the <code>java.beans</code> package.
1755      * Please see {@link java.beans.XMLEncoder}.
1756      */
1757     @SuppressWarnings("serial") // Same-version serialization only
1758     protected class AccessibleJViewport extends AccessibleJComponent {
1759         /**
1760          * Get the role of this object.
1761          *
1762          * @return an instance of AccessibleRole describing the role of
1763          * the object
1764          */
1765         public AccessibleRole getAccessibleRole() {
1766             return AccessibleRole.VIEWPORT;
1767         }
1768     } // inner class AccessibleJViewport
1769 }