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