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