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