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