001    /* JViewport.java -- 
002       Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import gnu.classpath.SystemProperties;
042    
043    import java.awt.Component;
044    import java.awt.Dimension;
045    import java.awt.Graphics;
046    import java.awt.Image;
047    import java.awt.Insets;
048    import java.awt.LayoutManager;
049    import java.awt.Point;
050    import java.awt.Rectangle;
051    import java.awt.Shape;
052    import java.awt.event.ComponentAdapter;
053    import java.awt.event.ComponentEvent;
054    import java.io.Serializable;
055    
056    import javax.accessibility.Accessible;
057    import javax.accessibility.AccessibleContext;
058    import javax.accessibility.AccessibleRole;
059    import javax.swing.border.Border;
060    import javax.swing.event.ChangeEvent;
061    import javax.swing.event.ChangeListener;
062    import 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     */
105    public 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    }