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