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