001/* JViewport.java --
002   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing;
040
041import gnu.classpath.SystemProperties;
042
043import java.awt.Component;
044import java.awt.Dimension;
045import java.awt.Graphics;
046import java.awt.Image;
047import java.awt.Insets;
048import java.awt.LayoutManager;
049import java.awt.Point;
050import java.awt.Rectangle;
051import java.awt.Shape;
052import java.awt.event.ComponentAdapter;
053import java.awt.event.ComponentEvent;
054import java.io.Serializable;
055
056import javax.accessibility.Accessible;
057import javax.accessibility.AccessibleContext;
058import javax.accessibility.AccessibleRole;
059import javax.swing.border.Border;
060import javax.swing.event.ChangeEvent;
061import javax.swing.event.ChangeListener;
062import javax.swing.plaf.ViewportUI;
063
064/**
065 *
066 * <pre>
067 *                                                     _
068 *   +-------------------------------+    ...........Y1 \
069 *   |  view                         |                .  \
070 *   |  (this component's child)     |                .   > VY
071 *   |                               |                .  / = Y2-Y1
072 *   |         +------------------------------+  ....Y2_/
073 *   |         | viewport            |        |       .
074 *   |         | (this component)    |        |       .
075 *   |         |                     |        |       .
076 *   |         |                     |        |       .
077 *   |         |                     |        |       .
078 *   |         |                     |        |       .
079 *   |         +------------------------------+  ....Y3
080 *   |                               |                .
081 *   |         .                     |        .       .
082 *   |         .                     |        .       .
083 *   +---------.---------------------+    ...........Y4
084 *   .         .                     .        .
085 *   .         .                     .        .
086 *   .         .                     .        .
087 *   X1.......X2.....................X3.......X4
088 *   \____  ___/
089 *        \/
090 *        VX = X2-X1
091 *</pre>
092 *
093 * <p>A viewport is, like all swing components, located at some position in
094 * the swing component tree; that location is exactly the same as any other
095 * components: the viewport's "bounds".</p>
096 *
097 * <p>But in terms of drawing its child, the viewport thinks of itself as
098 * covering a particular position <em>of the view's coordinate space</em>.
099 * For example, the {@link #getViewPosition} method returns
100 * the position <code>(VX,VY)</code> shown above, which is an position in
101 * "view space", even though this is <em>implemented</em> by positioning
102 * the underlying child at position <code>(-VX,-VY)</code></p>
103 *
104 */
105public class JViewport extends JComponent implements Accessible
106{
107  /**
108   * Provides accessibility support for <code>JViewport</code>.
109   *
110   * @author Roman Kennke (roman@kennke.org)
111   */
112  protected class AccessibleJViewport extends AccessibleJComponent
113  {
114    /**
115     * Creates a new instance of <code>AccessibleJViewport</code>.
116     */
117    protected AccessibleJViewport()
118    {
119      // Nothing to do here.
120    }
121
122    /**
123     * Returns the accessible role of <code>JViewport</code>, which is
124     * {@link AccessibleRole#VIEWPORT}.
125     *
126     * @return the accessible role of <code>JViewport</code>
127     */
128    public AccessibleRole getAccessibleRole()
129    {
130      return AccessibleRole.VIEWPORT;
131    }
132  }
133
134  /**
135   * A {@link java.awt.event.ComponentListener} that listens for
136   * changes of the view's size. This triggers a revalidate() call on the
137   * viewport.
138   */
139  protected class ViewListener extends ComponentAdapter implements Serializable
140  {
141    private static final long serialVersionUID = -2812489404285958070L;
142
143    /**
144     * Creates a new instance of ViewListener.
145     */
146    protected ViewListener()
147    {
148      // Nothing to do here.
149    }
150
151    /**
152     * Receives notification when a component (in this case: the view
153     * component) changes it's size. This simply triggers a revalidate() on the
154     * viewport.
155     *
156     * @param ev the ComponentEvent describing the change
157     */
158    public void componentResized(ComponentEvent ev)
159    {
160      // Fire state change, because resizing the view means changing the
161      // extentSize.
162      fireStateChanged();
163      revalidate();
164    }
165  }
166
167  public static final int SIMPLE_SCROLL_MODE = 0;
168  public static final int BLIT_SCROLL_MODE = 1;
169  public static final int BACKINGSTORE_SCROLL_MODE = 2;
170
171  private static final long serialVersionUID = -6925142919680527970L;
172
173  /**
174   * The default scrollmode to be used by all JViewports as determined by
175   * the system property gnu.javax.swing.JViewport.scrollMode.
176   */
177  private static final int defaultScrollMode;
178
179  protected boolean scrollUnderway;
180  protected boolean isViewSizeSet;
181
182  /**
183   * This flag indicates whether we use a backing store for drawing.
184   *
185   * @deprecated since JDK 1.3
186   */
187  protected boolean backingStore;
188
189  /**
190   * The backingstore image used for the backingstore and blit scroll methods.
191   */
192  protected Image backingStoreImage;
193
194  /**
195   * The position at which the view has been drawn the last time. This is used
196   * to determine the bittable area.
197   */
198  protected Point lastPaintPosition;
199
200  ChangeEvent changeEvent = new ChangeEvent(this);
201
202  int scrollMode;
203
204  /**
205   * The ViewListener instance.
206   */
207  ViewListener viewListener;
208
209  /**
210   * Stores the location from where to blit. This is a cached Point object used
211   * in blitting calculations.
212   */
213  Point cachedBlitFrom;
214
215  /**
216   * Stores the location where to blit to. This is a cached Point object used
217   * in blitting calculations.
218   */
219  Point cachedBlitTo;
220
221  /**
222   * Stores the width of the blitted area. This is a cached Dimension object
223   * used in blitting calculations.
224   */
225  Dimension cachedBlitSize;
226
227  /**
228   * Stores the bounds of the area that needs to be repainted. This is a cached
229   * Rectangle object used in blitting calculations.
230   */
231  Rectangle cachedBlitPaint;
232
233  boolean damaged = true;
234
235  /**
236   * A flag indicating if the size of the viewport has changed since the
237   * last repaint. This is used in double buffered painting to check if we
238   * need a new double buffer, or can reuse the old one.
239   */
240  boolean sizeChanged = true;
241
242  /**
243   * Indicates if this JViewport is the paint root or not. If it is not, then
244   * we may not assume that the offscreen buffer still has the right content
245   * because parent components may have cleared the background already.
246   */
247  private boolean isPaintRoot = false;
248
249  /**
250   * Initializes the default setting for the scrollMode property.
251   */
252  static
253  {
254    String scrollModeProp =
255      SystemProperties.getProperty("gnu.swing.scrollmode", "BACKINGSTORE");
256    if (scrollModeProp.equalsIgnoreCase("simple"))
257      defaultScrollMode = SIMPLE_SCROLL_MODE;
258    else if (scrollModeProp.equalsIgnoreCase("backingstore"))
259      defaultScrollMode = BACKINGSTORE_SCROLL_MODE;
260    else
261      defaultScrollMode = BLIT_SCROLL_MODE;
262  }
263
264  public JViewport()
265  {
266    setOpaque(true);
267    setScrollMode(defaultScrollMode);
268    updateUI();
269    setLayout(createLayoutManager());
270    lastPaintPosition = new Point();
271    cachedBlitFrom = new Point();
272    cachedBlitTo = new Point();
273    cachedBlitSize = new Dimension();
274    cachedBlitPaint = new Rectangle();
275  }
276
277  public Dimension getExtentSize()
278  {
279    return getSize();
280  }
281
282  public Dimension toViewCoordinates(Dimension size)
283  {
284    return size;
285  }
286
287  public Point toViewCoordinates(Point p)
288  {
289    Point pos = getViewPosition();
290    return new Point(p.x + pos.x,
291                     p.y + pos.y);
292  }
293
294  public void setExtentSize(Dimension newSize)
295  {
296    Dimension oldExtent = getExtentSize();
297    if (! newSize.equals(oldExtent))
298      {
299        setSize(newSize);
300        fireStateChanged();
301      }
302  }
303
304  /**
305   * Returns the viewSize when set, or the preferred size of the set
306   * Component view.  If no viewSize and no Component view is set an
307   * empty Dimension is returned.
308   */
309  public Dimension getViewSize()
310  {
311    Dimension size;
312    Component view = getView();
313    if (view != null)
314      {
315        if (isViewSizeSet)
316          size = view.getSize();
317        else
318          size = view.getPreferredSize();
319      }
320    else
321      size = new Dimension(0, 0);
322    return size;
323  }
324
325
326  public void setViewSize(Dimension newSize)
327  {
328    Component view = getView();
329    if (view != null)
330      {
331        if (! newSize.equals(view.getSize()))
332          {
333            scrollUnderway = false;
334            view.setSize(newSize);
335            isViewSizeSet = true;
336            fireStateChanged();
337          }
338      }
339  }
340
341  /**
342   * Get the viewport's position in view space. Despite confusing name,
343   * this really does return the viewport's (0,0) position in view space,
344   * not the view's position.
345   */
346
347  public Point getViewPosition()
348  {
349    Component view = getView();
350    if (view == null)
351      return new Point(0,0);
352    else
353      {
354        Point p = view.getLocation();
355        p.x = -p.x;
356        p.y = -p.y;
357        return p;
358      }
359  }
360
361  public void setViewPosition(Point p)
362  {
363    Component view = getView();
364    if (view != null && ! p.equals(getViewPosition()))
365      {
366        scrollUnderway = true;
367        view.setLocation(-p.x, -p.y);
368        fireStateChanged();
369      }
370  }
371
372  public Rectangle getViewRect()
373  {
374    return new Rectangle(getViewPosition(), getExtentSize());
375  }
376
377  /**
378   * @deprecated 1.4
379   */
380  public boolean isBackingStoreEnabled()
381  {
382    return scrollMode == BACKINGSTORE_SCROLL_MODE;
383  }
384
385  /**
386   * @deprecated 1.4
387   */
388  public void setBackingStoreEnabled(boolean b)
389  {
390    if (b && scrollMode != BACKINGSTORE_SCROLL_MODE)
391      {
392        scrollMode = BACKINGSTORE_SCROLL_MODE;
393        fireStateChanged();
394      }
395  }
396
397  public void setScrollMode(int mode)
398  {
399    scrollMode = mode;
400    fireStateChanged();
401  }
402
403  public int getScrollMode()
404  {
405    return scrollMode;
406  }
407
408  public Component getView()
409  {
410    if (getComponentCount() == 0)
411      return null;
412
413    return getComponents()[0];
414  }
415
416  public void setView(Component v)
417  {
418    Component currView = getView();
419    if (viewListener != null && currView != null)
420      currView.removeComponentListener(viewListener);
421
422    if (v != null)
423      {
424        if (viewListener == null)
425          viewListener = createViewListener();
426        v.addComponentListener(viewListener);
427        add(v);
428        fireStateChanged();
429      }
430    revalidate();
431    repaint();
432  }
433
434  public void reshape(int x, int y, int w, int h)
435  {
436    if (w != getWidth() || h != getHeight())
437      sizeChanged = true;
438    super.reshape(x, y, w, h);
439    if (sizeChanged)
440      {
441        damaged = true;
442        fireStateChanged();
443      }
444  }
445
446  public final Insets getInsets()
447  {
448    return new Insets(0, 0, 0, 0);
449  }
450
451  public final Insets getInsets(Insets insets)
452  {
453    if (insets == null)
454      return getInsets();
455    insets.top = 0;
456    insets.bottom = 0;
457    insets.left = 0;
458    insets.right = 0;
459    return insets;
460  }
461
462
463  /**
464   * Overridden to return <code>false</code>, so the JViewport's paint method
465   * gets called instead of directly calling the children. This is necessary
466   * in order to get a useful clipping and translation on the children.
467   *
468   * @return <code>false</code>
469   */
470  public boolean isOptimizedDrawingEnabled()
471  {
472    return false;
473  }
474
475  public void paint(Graphics g)
476  {
477    Component view = getView();
478
479    if (view == null)
480      return;
481
482    Rectangle viewBounds = view.getBounds();
483    Rectangle portBounds = getBounds();
484
485    if (viewBounds.width == 0
486        || viewBounds.height == 0
487        || portBounds.width == 0
488        || portBounds.height == 0)
489      return;
490
491    switch (getScrollMode())
492      {
493
494      case JViewport.BACKINGSTORE_SCROLL_MODE:
495        paintBackingStore(g);
496        break;
497      case JViewport.BLIT_SCROLL_MODE:
498        paintBlit(g);
499        break;
500      case JViewport.SIMPLE_SCROLL_MODE:
501      default:
502        paintSimple(g);
503        break;
504      }
505    damaged = false;
506  }
507
508  public void addChangeListener(ChangeListener listener)
509  {
510    listenerList.add(ChangeListener.class, listener);
511  }
512
513  public void removeChangeListener(ChangeListener listener)
514  {
515    listenerList.remove(ChangeListener.class, listener);
516  }
517
518  public ChangeListener[] getChangeListeners()
519  {
520    return (ChangeListener[]) getListeners(ChangeListener.class);
521  }
522
523  /**
524   * This method returns the String ID of the UI class of  Separator.
525   *
526   * @return The UI class' String ID.
527   */
528  public String getUIClassID()
529  {
530    return "ViewportUI";
531  }
532
533  /**
534   * This method resets the UI used to the Look and Feel defaults..
535   */
536  public void updateUI()
537  {
538    setUI((ViewportUI) UIManager.getUI(this));
539  }
540
541  /**
542   * This method returns the viewport's UI delegate.
543   *
544   * @return The viewport's UI delegate.
545   */
546  public ViewportUI getUI()
547  {
548    return (ViewportUI) ui;
549  }
550
551  /**
552   * This method sets the viewport's UI delegate.
553   *
554   * @param ui The viewport's UI delegate.
555   */
556  public void setUI(ViewportUI ui)
557  {
558    super.setUI(ui);
559  }
560
561  public final void setBorder(Border border)
562  {
563    if (border != null)
564      throw new IllegalArgumentException();
565  }
566
567  /**
568   * Scrolls the view so that contentRect becomes visible.
569   *
570   * @param contentRect the rectangle to make visible within the view
571   */
572  public void scrollRectToVisible(Rectangle contentRect)
573  {
574    Component view = getView();
575    if (view == null)
576      return;
577
578    Point pos = getViewPosition();
579    // We get the contentRect in the viewport coordinates. But we want to
580    // calculate with view coordinates.
581    int contentX = contentRect.x + pos.x;
582    int contentY = contentRect.y + pos.y;
583    Rectangle viewBounds = getView().getBounds();
584    Rectangle portBounds = getBounds();
585
586    if (isShowing())
587      getView().validate();
588
589    // If the bottom boundary of contentRect is below the port
590    // boundaries, scroll up as necessary.
591    if (contentY + contentRect.height + viewBounds.y > portBounds.height)
592      pos.y = contentY + contentRect.height - portBounds.height;
593    // If contentY is above the port boundaries, scroll down to
594    // contentY.
595    if (contentY + viewBounds.y < 0)
596      pos.y = contentY;
597    // If the right boundary of contentRect is right from the port
598    // boundaries, scroll left as necessary.
599    if (contentX + contentRect.width + viewBounds.x > portBounds.width)
600      pos.x = contentX + contentRect.width - portBounds.width;
601    // If contentX is left from the port boundaries, scroll right to
602    // contentRect.x.
603    if (contentX + viewBounds.x < 0)
604      pos.x = contentX;
605    setViewPosition(pos);
606  }
607
608  /**
609   * Returns the accessible context for this <code>JViewport</code>. This
610   * will be an instance of {@link AccessibleJViewport}.
611   *
612   * @return the accessible context for this <code>JViewport</code>
613   */
614  public AccessibleContext getAccessibleContext()
615  {
616    if (accessibleContext == null)
617      accessibleContext = new AccessibleJViewport();
618    return accessibleContext;
619  }
620
621  /**
622   * Forward repaint to parent to make sure only one paint is performed by the
623   * RepaintManager.
624   *
625   * @param tm number of milliseconds to defer the repaint request
626   * @param x the X coordinate of the upper left corner of the dirty area
627   * @param y the Y coordinate of the upper left corner of the dirty area
628   * @param w the width of the dirty area
629   * @param h the height of the dirty area
630   */
631  public void repaint(long tm, int x, int y, int w, int h)
632  {
633    Component parent = getParent();
634    if (parent != null)
635      parent.repaint(tm, x + getX(), y + getY(), w, h);
636    else
637      super.repaint(tm, x, y, w, h);
638  }
639
640  protected void addImpl(Component comp, Object constraints, int index)
641  {
642    if (getComponentCount() > 0)
643      remove(getComponents()[0]);
644
645    super.addImpl(comp, constraints, index);
646  }
647
648  protected void fireStateChanged()
649  {
650    ChangeListener[] listeners = getChangeListeners();
651    for (int i = 0; i < listeners.length; ++i)
652      listeners[i].stateChanged(changeEvent);
653  }
654
655  /**
656   * Creates a {@link ViewListener} that is supposed to listen for
657   * size changes on the view component.
658   *
659   * @return a ViewListener instance
660   */
661  protected ViewListener createViewListener()
662  {
663    return new ViewListener();
664  }
665
666  /**
667   * Creates the LayoutManager that is used for this viewport. Override
668   * this method if you want to use a custom LayoutManager.
669   *
670   * @return a LayoutManager to use for this viewport
671   */
672  protected LayoutManager createLayoutManager()
673  {
674    return new ViewportLayout();
675  }
676
677  /**
678   * Computes the parameters for the blitting scroll method. <code>dx</code>
679   * and <code>dy</code> specifiy the X and Y offset by which the viewport
680   * is scrolled. All other arguments are output parameters and are filled by
681   * this method.
682   *
683   * <code>blitFrom</code> holds the position of the blit rectangle in the
684   * viewport rectangle before scrolling, <code>blitTo</code> where the blitArea
685   * is copied to.
686   *
687   * <code>blitSize</code> holds the size of the blit area and
688   * <code>blitPaint</code> is the area of the view that needs to be painted.
689   *
690   * This method returns <code>true</code> if blitting is possible and
691   * <code>false</code> if the viewport has to be repainted completetly without
692   * blitting.
693   *
694   * @param dx the horizontal delta
695   * @param dy the vertical delta
696   * @param blitFrom the position from where to blit; set by this method
697   * @param blitTo the position where to blit area is copied to; set by this
698   *        method
699   * @param blitSize the size of the blitted area; set by this method
700   * @param blitPaint the area that needs repainting; set by this method
701   *
702   * @return <code>true</code> if blitting is possible,
703   *         <code>false</code> otherwise
704   */
705  protected boolean computeBlit(int dx, int dy, Point blitFrom, Point blitTo,
706                                Dimension blitSize, Rectangle blitPaint)
707  {
708    if ((dx != 0 && dy != 0) || (dy == 0 && dy == 0) || damaged)
709      // We cannot blit if the viewport is scrolled in both directions at
710      // once. Also, we do not want to blit if the viewport is not scrolled at
711      // all, because that probably means the view component repaints itself
712      // and the buffer needs updating.
713      return false;
714
715    Rectangle portBounds = SwingUtilities.calculateInnerArea(this, getBounds());
716
717    // Compute the blitFrom and blitTo parameters.
718    blitFrom.x = portBounds.x;
719    blitFrom.y = portBounds.y;
720    blitTo.x = portBounds.x;
721    blitTo.y = portBounds.y;
722
723    if (dy > 0)
724      {
725        blitFrom.y = portBounds.y + dy;
726      }
727    else if (dy < 0)
728      {
729        blitTo.y = portBounds.y - dy;
730      }
731    else if (dx > 0)
732      {
733        blitFrom.x = portBounds.x + dx;
734      }
735    else if (dx < 0)
736      {
737        blitTo.x = portBounds.x - dx;
738      }
739
740    // Compute size of the blit area.
741    if (dx != 0)
742      {
743        blitSize.width = portBounds.width - Math.abs(dx);
744        blitSize.height = portBounds.height;
745      }
746    else if (dy != 0)
747      {
748        blitSize.width = portBounds.width;
749        blitSize.height = portBounds.height - Math.abs(dy);
750      }
751
752    // Compute the blitPaint parameter.
753    blitPaint.setBounds(portBounds);
754    if (dy > 0)
755      {
756        blitPaint.y = portBounds.y + portBounds.height - dy;
757        blitPaint.height = dy;
758      }
759    else if (dy < 0)
760      {
761        blitPaint.height = -dy;
762      }
763    if (dx > 0)
764      {
765        blitPaint.x = portBounds.x + portBounds.width - dx;
766        blitPaint.width = dx;
767      }
768    else if (dx < 0)
769      {
770        blitPaint.width = -dx;
771      }
772
773    return true;
774  }
775
776  /**
777   * Paints the viewport in case we have a scrollmode of
778   * {@link #SIMPLE_SCROLL_MODE}.
779   *
780   * This simply paints the view directly on the surface of the viewport.
781   *
782   * @param g the graphics context to use
783   */
784  void paintSimple(Graphics g)
785  {
786    // We need to call this to properly clear the background.
787    paintComponent(g);
788
789    Point pos = getViewPosition();
790    Component view = getView();
791    Shape oldClip = g.getClip();
792    g.clipRect(0, 0, getWidth(), getHeight());
793    boolean translated = false;
794    try
795      {
796        g.translate(-pos.x, -pos.y);
797        translated = true;
798        view.paint(g);
799      }
800    finally
801      {
802        if (translated)
803          g.translate (pos.x, pos.y);
804        g.setClip(oldClip);
805      }
806  }
807
808  /**
809   * Paints the viewport in case we have a scroll mode of
810   * {@link #BACKINGSTORE_SCROLL_MODE}.
811   *
812   * This method uses a backing store image to paint the view to, which is then
813   * subsequently painted on the screen. This should make scrolling more
814   * smooth.
815   *
816   * @param g the graphics context to use
817   */
818  void paintBackingStore(Graphics g)
819  {
820    // If we have no backing store image yet or the size of the component has
821    // changed, we need to rebuild the backing store.
822    if (backingStoreImage == null || sizeChanged)
823      {
824        backingStoreImage = createImage(getWidth(), getHeight());
825        sizeChanged = false;
826        Graphics g2 = backingStoreImage.getGraphics();
827        paintSimple(g2);
828        g2.dispose();
829      }
830    // Otherwise we can perform the blitting on the backing store image:
831    // First we move the part that remains visible after scrolling, then
832    // we only need to paint the bit that becomes newly visible.
833    else
834      {
835        Graphics g2 = backingStoreImage.getGraphics();
836        Point viewPosition = getViewPosition();
837        int dx = viewPosition.x - lastPaintPosition.x;
838        int dy = viewPosition.y - lastPaintPosition.y;
839        boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo,
840                                      cachedBlitSize, cachedBlitPaint);
841        if (canBlit && isPaintRoot)
842          {
843            // Copy the part that remains visible during scrolling.
844            if (cachedBlitSize.width > 0 && cachedBlitSize.height > 0)
845              {
846                g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y,
847                            cachedBlitSize.width, cachedBlitSize.height,
848                            cachedBlitTo.x - cachedBlitFrom.x,
849                            cachedBlitTo.y - cachedBlitFrom.y);
850              }
851            // Now paint the part that becomes newly visible.
852            g2.setClip(cachedBlitPaint.x, cachedBlitPaint.y,
853                       cachedBlitPaint.width, cachedBlitPaint.height);
854            paintSimple(g2);
855          }
856        // If blitting is not possible for some reason, fall back to repainting
857        // everything.
858        else
859          {
860            // If the image has not been scrolled at all, only the changed
861            // clip must be updated in the buffer.
862            if (dx == 0 && dy == 0)
863              g2.setClip(g.getClip());
864
865            paintSimple(g2);
866          }
867        g2.dispose();
868      }
869    // Actually draw the backingstore image to the graphics context.
870    g.drawImage(backingStoreImage, 0, 0, this);
871    // Update the lastPaintPosition so that we know what is already drawn when
872    // we paint the next time.
873    lastPaintPosition.setLocation(getViewPosition());
874  }
875
876  /**
877   * Paints the viewport in case we have a scrollmode of
878   * {@link #BLIT_SCROLL_MODE}.
879   *
880   * This paints the viewport using a backingstore and a blitting algorithm.
881   * Only the newly exposed area of the view is painted from the view painting
882   * methods, the remainder is copied from the backing store.
883   *
884   * @param g the graphics context to use
885   */
886  void paintBlit(Graphics g)
887  {
888    // First we move the part that remains visible after scrolling, then
889    // we only need to paint the bit that becomes newly visible.
890    Point viewPosition = getViewPosition();
891    int dx = viewPosition.x - lastPaintPosition.x;
892    int dy = viewPosition.y - lastPaintPosition.y;
893    boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo,
894                                  cachedBlitSize, cachedBlitPaint);
895    if (canBlit && isPaintRoot)
896      {
897        // Copy the part that remains visible during scrolling.
898        if (cachedBlitSize.width > 0 && cachedBlitSize.width > 0)
899          {
900            g.copyArea(cachedBlitFrom.x, cachedBlitFrom.y,
901                       cachedBlitSize.width, cachedBlitSize.height,
902                       cachedBlitTo.x - cachedBlitFrom.x,
903                       cachedBlitTo.y - cachedBlitFrom.y);
904          }
905        // Now paint the part that becomes newly visible.
906        Shape oldClip = g.getClip();
907        g.clipRect(cachedBlitPaint.x, cachedBlitPaint.y,
908                  cachedBlitPaint.width, cachedBlitPaint.height);
909        try
910          {
911            paintSimple(g);
912          }
913        finally
914          {
915            g.setClip(oldClip);
916          }
917      }
918    // If blitting is not possible for some reason, fall back to repainting
919    // everything.
920    else
921      paintSimple(g);
922    lastPaintPosition.setLocation(getViewPosition());
923  }
924
925  /**
926   * Overridden from JComponent to set the {@link #isPaintRoot} flag.
927   *
928   * @param x the rectangle to paint, X coordinate
929   * @param y the rectangle to paint, Y coordinate
930   * @param w the rectangle to paint, width
931   * @param h the rectangle to paint, height
932   */
933  void paintImmediately2(int x, int y, int w, int h)
934  {
935    isPaintRoot = true;
936    super.paintImmediately2(x, y, w, h);
937    isPaintRoot = false;
938  }
939
940  /**
941   * Returns true when the JViewport is using a backbuffer, so that we
942   * can update our backbuffer correctly.
943   */
944  boolean isPaintRoot()
945  {
946    return scrollMode == BACKINGSTORE_SCROLL_MODE;
947  }
948}