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