Source for javax.swing.JViewport

   1: /* JViewport.java -- 
   2:    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing;
  40: 
  41: import java.awt.Component;
  42: import java.awt.Dimension;
  43: import java.awt.Graphics;
  44: import java.awt.Image;
  45: import java.awt.Insets;
  46: import java.awt.LayoutManager;
  47: import java.awt.Point;
  48: import java.awt.Rectangle;
  49: import java.awt.event.ComponentAdapter;
  50: import java.awt.event.ComponentEvent;
  51: import java.io.Serializable;
  52: 
  53: import javax.accessibility.Accessible;
  54: import javax.accessibility.AccessibleContext;
  55: import javax.accessibility.AccessibleRole;
  56: import javax.swing.border.Border;
  57: import javax.swing.event.ChangeEvent;
  58: import javax.swing.event.ChangeListener;
  59: import javax.swing.plaf.ViewportUI;
  60: 
  61: /**
  62:  *  
  63:  * <pre>
  64:  *                                                     _
  65:  *   +-------------------------------+    ...........Y1 \
  66:  *   |  view                         |                .  \
  67:  *   |  (this component's child)     |                .   > VY
  68:  *   |                               |                .  / = Y2-Y1
  69:  *   |         +------------------------------+  ....Y2_/
  70:  *   |         | viewport            |        |       .
  71:  *   |         | (this component)    |        |       .
  72:  *   |         |                     |        |       .
  73:  *   |         |                     |        |       .
  74:  *   |         |                     |        |       .
  75:  *   |         |                     |        |       .
  76:  *   |         +------------------------------+  ....Y3
  77:  *   |                               |                .
  78:  *   |         .                     |        .       .
  79:  *   |         .                     |        .       .
  80:  *   +---------.---------------------+    ...........Y4
  81:  *   .         .                     .        .
  82:  *   .         .                     .        .
  83:  *   .         .                     .        .
  84:  *   X1.......X2.....................X3.......X4
  85:  *   \____  ___/
  86:  *        \/
  87:  *        VX = X2-X1
  88:  *</pre>
  89:  *  
  90:  * <p>A viewport is, like all swing components, located at some position in
  91:  * the swing component tree; that location is exactly the same as any other
  92:  * components: the viewport's "bounds".</p>
  93:  *
  94:  * <p>But in terms of drawing its child, the viewport thinks of itself as
  95:  * covering a particular position <em>of the view's coordinate space</em>.
  96:  * For example, the {@link #getViewPosition} method returns
  97:  * the position <code>(VX,VY)</code> shown above, which is an position in
  98:  * "view space", even though this is <em>implemented</em> by positioning
  99:  * the underlying child at position <code>(-VX,-VY)</code></p>
 100:  *
 101:  */
 102: public class JViewport extends JComponent implements Accessible
 103: {
 104:   /**
 105:    * Provides accessibility support for <code>JViewport</code>.
 106:    *
 107:    * @author Roman Kennke (roman@kennke.org)
 108:    */
 109:   protected class AccessibleJViewport extends AccessibleJComponent
 110:   {
 111:     /**
 112:      * Creates a new instance of <code>AccessibleJViewport</code>.
 113:      */
 114:     public AccessibleJViewport()
 115:     {
 116:       // Nothing to do here.
 117:     }
 118: 
 119:     /**
 120:      * Returns the accessible role of <code>JViewport</code>, which is
 121:      * {@link AccessibleRole#VIEWPORT}.
 122:      *
 123:      * @return the accessible role of <code>JViewport</code>
 124:      */
 125:     public AccessibleRole getAccessibleRole()
 126:     {
 127:       return AccessibleRole.VIEWPORT;
 128:     }
 129:   }
 130: 
 131:   /**
 132:    * A {@link java.awt.event.ComponentListener} that listens for
 133:    * changes of the view's size. This triggers a revalidate() call on the
 134:    * viewport.
 135:    */
 136:   protected class ViewListener extends ComponentAdapter implements Serializable
 137:   {
 138:     private static final long serialVersionUID = -2812489404285958070L;
 139: 
 140:     /**
 141:      * Creates a new instance of ViewListener.
 142:      */
 143:     protected ViewListener()
 144:     {
 145:       // Nothing to do here.
 146:     }
 147: 
 148:     /**
 149:      * Receives notification when a component (in this case: the view
 150:      * component) changes it's size. This simply triggers a revalidate() on the
 151:      * viewport.
 152:      *
 153:      * @param ev the ComponentEvent describing the change
 154:      */
 155:     public void componentResized(ComponentEvent ev)
 156:     {
 157:       revalidate();
 158:     }
 159:   }
 160: 
 161:   public static final int SIMPLE_SCROLL_MODE = 0;
 162:   public static final int BLIT_SCROLL_MODE = 1;
 163:   public static final int BACKINGSTORE_SCROLL_MODE = 2;
 164: 
 165:   private static final long serialVersionUID = -6925142919680527970L;
 166:   
 167:   protected boolean scrollUnderway;
 168:   protected boolean isViewSizeSet;
 169: 
 170:   /**
 171:    * This flag indicates whether we use a backing store for drawing.
 172:    *
 173:    * @deprecated since JDK 1.3
 174:    */
 175:   protected boolean backingStore;
 176: 
 177:   /**
 178:    * The backingstore image used for the backingstore and blit scroll methods.
 179:    */
 180:   protected Image backingStoreImage;
 181: 
 182:   /**
 183:    * The position at which the view has been drawn the last time. This is used
 184:    * to determine the bittable area.
 185:    */
 186:   protected Point lastPaintPosition;
 187: 
 188:   ChangeEvent changeEvent = new ChangeEvent(this);
 189: 
 190:   int scrollMode;
 191: 
 192:   /** 
 193:    * The width and height of the Viewport's area in terms of view
 194:    * coordinates.  Typically this will be the same as the width and height
 195:    * of the viewport's bounds, unless the viewport transforms units of
 196:    * width and height, which it may do, for example if it magnifies or
 197:    * rotates its view.
 198:    *
 199:    * @see #toViewCoordinates(Dimension)
 200:    */
 201:   Dimension extentSize;
 202: 
 203:   /**
 204:    * The width and height of the view in its own coordinate space.
 205:    */
 206:   Dimension viewSize;
 207: 
 208:   /**
 209:    * The ViewListener instance.
 210:    */
 211:   ViewListener viewListener;
 212: 
 213:   /**
 214:    * Stores the location from where to blit. This is a cached Point object used
 215:    * in blitting calculations.
 216:    */
 217:   Point cachedBlitFrom;
 218: 
 219:   /**
 220:    * Stores the location where to blit to. This is a cached Point object used
 221:    * in blitting calculations.
 222:    */
 223:   Point cachedBlitTo;
 224: 
 225:   /**
 226:    * Stores the width of the blitted area. This is a cached Dimension object
 227:    * used in blitting calculations.
 228:    */
 229:   Dimension cachedBlitSize;
 230: 
 231:   /**
 232:    * Stores the bounds of the area that needs to be repainted. This is a cached
 233:    * Rectangle object used in blitting calculations. 
 234:    */
 235:   Rectangle cachedBlitPaint;
 236: 
 237:   boolean damaged = true;
 238: 
 239:   /**
 240:    * A flag indicating if the size of the viewport has changed since the
 241:    * last repaint. This is used in double buffered painting to check if we
 242:    * need a new double buffer, or can reuse the old one.
 243:    */
 244:   boolean sizeChanged = true;
 245: 
 246:   public JViewport()
 247:   {
 248:     setOpaque(true);
 249:     setScrollMode(BLIT_SCROLL_MODE);
 250:     updateUI();
 251:     setLayout(createLayoutManager());
 252:     lastPaintPosition = new Point();
 253:     cachedBlitFrom = new Point();
 254:     cachedBlitTo = new Point();
 255:     cachedBlitSize = new Dimension();
 256:     cachedBlitPaint = new Rectangle();
 257:   }
 258: 
 259:   public Dimension getExtentSize()
 260:   {
 261:     if (extentSize == null)
 262:       return toViewCoordinates(getSize());
 263:     else
 264:       return extentSize;
 265:   }
 266: 
 267:   public Dimension toViewCoordinates(Dimension size)
 268:   {
 269:     return size;
 270:   }
 271: 
 272:   public Point toViewCoordinates(Point p)
 273:   {
 274:     Point pos = getViewPosition();
 275:     return new Point(p.x + pos.x,
 276:                      p.y + pos.y);
 277:   }
 278: 
 279:   public void setExtentSize(Dimension newSize)
 280:   {
 281:     extentSize = newSize;
 282:     fireStateChanged();
 283:   }
 284: 
 285:   /**
 286:    * Returns the viewSize when set, or the preferred size of the set
 287:    * Component view.  If no viewSize and no Component view is set an
 288:    * empty Dimension is returned.
 289:    */
 290:   public Dimension getViewSize()
 291:   {
 292:     if (isViewSizeSet)
 293:       return viewSize;
 294:     else
 295:       {
 296:     Component view = getView();
 297:     if (view != null)
 298:       return view.getPreferredSize();
 299:     else
 300:       return new Dimension();
 301:       }
 302:   }
 303: 
 304: 
 305:   public void setViewSize(Dimension newSize)
 306:   {
 307:     viewSize = newSize;
 308:     Component view = getView();
 309:     if (view != null)
 310:       {
 311:         if (newSize != view.getSize())
 312:           {
 313:             view.setSize(viewSize);
 314:             fireStateChanged();
 315:           }
 316:       }
 317:     isViewSizeSet = true;
 318:   }
 319: 
 320:   /**
 321:    * Get the viewport's position in view space. Despite confusing name,
 322:    * this really does return the viewport's (0,0) position in view space,
 323:    * not the view's position.
 324:    */
 325: 
 326:   public Point getViewPosition()
 327:   {
 328:     Component view = getView();
 329:     if (view == null)
 330:       return new Point(0,0);
 331:     else
 332:       {
 333:         Point p = view.getLocation();
 334:         p.x = -p.x;
 335:         p.y = -p.y;
 336:         return p;
 337:       }
 338:   }
 339: 
 340:   public void setViewPosition(Point p)
 341:   {
 342:     if (getViewPosition().equals(p))
 343:       return;
 344:     Component view = getView();
 345:     if (view != null)
 346:       {
 347:         Point q = new Point(-p.x, -p.y);
 348:         view.setLocation(q);
 349:         isViewSizeSet = false;
 350:         fireStateChanged();
 351:       }
 352:     repaint();
 353:   }
 354: 
 355:   public Rectangle getViewRect()
 356:   {
 357:     return new Rectangle(getViewPosition(), 
 358:                          getExtentSize());
 359:   }
 360: 
 361:   /**
 362:    * @deprecated 1.4
 363:    */
 364:   public boolean isBackingStoreEnabled()
 365:   {
 366:     return scrollMode == BACKINGSTORE_SCROLL_MODE;
 367:   }
 368: 
 369:   /**
 370:    * @deprecated 1.4
 371:    */
 372:   public void setBackingStoreEnabled(boolean b)
 373:   {
 374:     if (b && scrollMode != BACKINGSTORE_SCROLL_MODE)
 375:       {
 376:         scrollMode = BACKINGSTORE_SCROLL_MODE;
 377:         fireStateChanged();
 378:       }
 379:   }
 380: 
 381:   public void setScrollMode(int mode)
 382:   {
 383:     scrollMode = mode;
 384:     fireStateChanged();
 385:   }
 386: 
 387:   public int getScrollMode()
 388:   {
 389:     return scrollMode;
 390:   }
 391: 
 392:   public Component getView()
 393:   {
 394:     if (getComponentCount() == 0)
 395:       return null;
 396:   
 397:     return getComponents()[0];
 398:   }
 399: 
 400:   public void setView(Component v)
 401:   {
 402:     if (viewListener != null)
 403:       getView().removeComponentListener(viewListener);
 404: 
 405:     if (v != null)
 406:       {
 407:         if (viewListener == null)
 408:           viewListener = createViewListener();
 409:         v.addComponentListener(viewListener);
 410:         add(v);
 411:         fireStateChanged();
 412:       }
 413:     revalidate();
 414:     repaint();
 415:   }
 416: 
 417:   public void reshape(int x, int y, int w, int h)
 418:   {
 419:     if (w != getWidth() || h != getHeight())
 420:       sizeChanged = true;
 421:     super.reshape(x, y, w, h);
 422:     if (sizeChanged)
 423:       {
 424:         damaged = true;
 425:         fireStateChanged();
 426:       }
 427:   }
 428: 
 429:   public final Insets getInsets()
 430:   {
 431:     return new Insets(0, 0, 0, 0);
 432:   }
 433: 
 434:   public final Insets getInsets(Insets insets)
 435:   {
 436:     if (insets == null)
 437:       return getInsets();
 438:     insets.top = 0;
 439:     insets.bottom = 0;
 440:     insets.left = 0;
 441:     insets.right = 0;
 442:     return insets;
 443:   }
 444:     
 445: 
 446:   /**
 447:    * Overridden to return <code>false</code>, so the JViewport's paint method
 448:    * gets called instead of directly calling the children. This is necessary
 449:    * in order to get a useful clipping and translation on the children.
 450:    *
 451:    * @return <code>false</code>
 452:    */
 453:   public boolean isOptimizedDrawingEnabled()
 454:   {
 455:     return false;
 456:   }
 457: 
 458:   public void paint(Graphics g)
 459:   {
 460:     Component view = getView();
 461: 
 462:     if (view == null)
 463:       return;
 464: 
 465:     Point pos = getViewPosition();
 466:     Rectangle viewBounds = view.getBounds();
 467:     Rectangle portBounds = getBounds();
 468: 
 469:     if (viewBounds.width == 0 
 470:         || viewBounds.height == 0
 471:         || portBounds.width == 0
 472:         || portBounds.height == 0)
 473:       return;
 474: 
 475:     switch (getScrollMode())
 476:       {
 477: 
 478:       case JViewport.BACKINGSTORE_SCROLL_MODE:
 479:         paintBackingStore(g);
 480:         break;
 481:       case JViewport.BLIT_SCROLL_MODE:
 482:         paintBlit(g);
 483:         break;
 484:       case JViewport.SIMPLE_SCROLL_MODE:
 485:       default:
 486:         paintSimple(g);
 487:         break;
 488:       }
 489:     damaged = false;
 490:   }
 491: 
 492:   public void addChangeListener(ChangeListener listener)
 493:   {
 494:     listenerList.add(ChangeListener.class, listener);
 495:   }
 496: 
 497:   public void removeChangeListener(ChangeListener listener)
 498:   {
 499:     listenerList.remove(ChangeListener.class, listener);
 500:   }
 501: 
 502:   public ChangeListener[] getChangeListeners() 
 503:   {
 504:     return (ChangeListener[]) getListeners(ChangeListener.class);
 505:   }
 506: 
 507:   /**
 508:    * This method returns the String ID of the UI class of  Separator.
 509:    *
 510:    * @return The UI class' String ID.
 511:    */
 512:   public String getUIClassID()
 513:   {
 514:     return "ViewportUI";
 515:   }
 516: 
 517:   /**
 518:    * This method resets the UI used to the Look and Feel defaults..
 519:    */
 520:   public void updateUI()
 521:   {
 522:     setUI((ViewportUI) UIManager.getUI(this));
 523:   }            
 524: 
 525:   /**
 526:    * This method returns the viewport's UI delegate.
 527:    *
 528:    * @return The viewport's UI delegate.
 529:    */
 530:   public ViewportUI getUI()
 531:   {
 532:     return (ViewportUI) ui;
 533:   }
 534: 
 535:   /**
 536:    * This method sets the viewport's UI delegate.
 537:    *
 538:    * @param ui The viewport's UI delegate.
 539:    */
 540:   public void setUI(ViewportUI ui)
 541:   {
 542:     super.setUI(ui);
 543:   }
 544: 
 545:   public final void setBorder(Border border)
 546:   {
 547:     if (border != null)
 548:       throw new IllegalArgumentException();
 549:   }
 550: 
 551:   /**
 552:    * Scrolls the view so that contentRect becomes visible.
 553:    *
 554:    * @param contentRect the rectangle to make visible within the view
 555:    */
 556:   public void scrollRectToVisible(Rectangle contentRect)
 557:   {
 558:     Component view = getView();
 559:     if (view == null)
 560:       return;    
 561:       
 562:     Point pos = getViewPosition();
 563:     Rectangle viewBounds = getView().getBounds();
 564:     Rectangle portBounds = getBounds();
 565:     
 566:     if (isShowing())
 567:       getView().validate();
 568: 
 569:     // If the bottom boundary of contentRect is below the port
 570:     // boundaries, scroll up as necessary.
 571:     if (contentRect.y + contentRect.height + viewBounds.y > portBounds.height)
 572:       pos.y = contentRect.y + contentRect.height - portBounds.height;
 573:     // If contentRect.y is above the port boundaries, scroll down to
 574:     // contentRect.y.
 575:     if (contentRect.y + viewBounds.y < 0)
 576:       pos.y = contentRect.y;
 577:     // If the right boundary of contentRect is right from the port
 578:     // boundaries, scroll left as necessary.
 579:     if (contentRect.x + contentRect.width + viewBounds.x > portBounds.width)
 580:       pos.x = contentRect.x + contentRect.width - portBounds.width;
 581:     // If contentRect.x is left from the port boundaries, scroll right to
 582:     // contentRect.x.
 583:     if (contentRect.x + viewBounds.x < 0)
 584:       pos.x = contentRect.x;
 585:     setViewPosition(pos);
 586:   }
 587: 
 588:   /**
 589:    * Returns the accessible context for this <code>JViewport</code>. This
 590:    * will be an instance of {@link AccessibleJViewport}.
 591:    *
 592:    * @return the accessible context for this <code>JViewport</code>
 593:    */
 594:   public AccessibleContext getAccessibleContext()
 595:   {
 596:     if (accessibleContext == null)
 597:       accessibleContext = new AccessibleJViewport();
 598:     return accessibleContext;
 599:   }
 600: 
 601:   /**
 602:    * Forward repaint to parent to make sure only one paint is performed by the
 603:    * RepaintManager.
 604:    *
 605:    * @param tm number of milliseconds to defer the repaint request
 606:    * @param x the X coordinate of the upper left corner of the dirty area
 607:    * @param y the Y coordinate of the upper left corner of the dirty area
 608:    * @param w the width of the dirty area
 609:    * @param h the height of the dirty area
 610:    */
 611:   public void repaint(long tm, int x, int y, int w, int h)
 612:   {
 613:     Component parent = getParent();
 614:     if (parent != null)
 615:       {
 616:         parent.repaint(tm, x + getX(), y + getY(), w, h);
 617:       }
 618:   }
 619: 
 620:   protected void addImpl(Component comp, Object constraints, int index)
 621:   {
 622:     if (getComponentCount() > 0)
 623:       remove(getComponents()[0]);
 624:     
 625:     super.addImpl(comp, constraints, index);
 626:   }
 627: 
 628:   protected void fireStateChanged()
 629:   {
 630:     ChangeListener[] listeners = getChangeListeners();
 631:     for (int i = 0; i < listeners.length; ++i)
 632:       listeners[i].stateChanged(changeEvent);
 633:   }
 634: 
 635:   /**
 636:    * Creates a {@link ViewListener} that is supposed to listen for
 637:    * size changes on the view component.
 638:    *
 639:    * @return a ViewListener instance
 640:    */
 641:   protected ViewListener createViewListener()
 642:   {
 643:     return new ViewListener();
 644:   }
 645: 
 646:   /**
 647:    * Creates the LayoutManager that is used for this viewport. Override
 648:    * this method if you want to use a custom LayoutManager.
 649:    *
 650:    * @return a LayoutManager to use for this viewport
 651:    */
 652:   protected LayoutManager createLayoutManager()
 653:   {
 654:     return new ViewportLayout();
 655:   }
 656: 
 657:   /**
 658:    * Computes the parameters for the blitting scroll method. <code>dx</code>
 659:    * and <code>dy</code> specifiy the X and Y offset by which the viewport
 660:    * is scrolled. All other arguments are output parameters and are filled by
 661:    * this method.
 662:    *
 663:    * <code>blitFrom</code> holds the position of the blit rectangle in the
 664:    * viewport rectangle before scrolling, <code>blitTo</code> where the blitArea
 665:    * is copied to.
 666:    *
 667:    * <code>blitSize</code> holds the size of the blit area and
 668:    * <code>blitPaint</code> is the area of the view that needs to be painted.
 669:    *
 670:    * This method returns <code>true</code> if blitting is possible and
 671:    * <code>false</code> if the viewport has to be repainted completetly without
 672:    * blitting.
 673:    *
 674:    * @param dx the horizontal delta
 675:    * @param dy the vertical delta
 676:    * @param blitFrom the position from where to blit; set by this method
 677:    * @param blitTo the position where to blit area is copied to; set by this
 678:    *        method
 679:    * @param blitSize the size of the blitted area; set by this method
 680:    * @param blitPaint the area that needs repainting; set by this method
 681:    *
 682:    * @return <code>true</code> if blitting is possible,
 683:    *         <code>false</code> otherwise
 684:    */
 685:   protected boolean computeBlit(int dx, int dy, Point blitFrom, Point blitTo,
 686:                                 Dimension blitSize, Rectangle blitPaint)
 687:   {
 688:     if ((dx != 0 && dy != 0) || damaged)
 689:       // We cannot blit if the viewport is scrolled in both directions at
 690:       // once.
 691:       return false;
 692: 
 693:     Rectangle portBounds = SwingUtilities.calculateInnerArea(this, getBounds());
 694: 
 695:     // Compute the blitFrom and blitTo parameters.
 696:     blitFrom.x = portBounds.x;
 697:     blitFrom.y = portBounds.y;
 698:     blitTo.x = portBounds.x;
 699:     blitTo.y = portBounds.y;
 700: 
 701:     if (dy > 0)
 702:       {
 703:         blitFrom.y = portBounds.y + dy;
 704:       }
 705:     else if (dy < 0)
 706:       {
 707:         blitTo.y = portBounds.y - dy;
 708:       }
 709:     else if (dx > 0)
 710:       {
 711:         blitFrom.x = portBounds.x + dx;
 712:       }
 713:     else if (dx < 0)
 714:       {
 715:         blitTo.x = portBounds.x - dx;
 716:       }
 717: 
 718:     // Compute size of the blit area.
 719:     if (dx != 0)
 720:       {
 721:         blitSize.width = portBounds.width - Math.abs(dx);
 722:         blitSize.height = portBounds.height;
 723:       }
 724:     else if (dy != 0)
 725:       {
 726:         blitSize.width = portBounds.width;
 727:         blitSize.height = portBounds.height - Math.abs(dy);
 728:       }
 729: 
 730:     // Compute the blitPaint parameter.
 731:     blitPaint.setBounds(portBounds);
 732:     if (dy > 0)
 733:       {
 734:         blitPaint.y = portBounds.y + portBounds.height - dy;
 735:         blitPaint.height = dy;
 736:       }
 737:     else if (dy < 0)
 738:       {
 739:         blitPaint.height = -dy;
 740:       }
 741:     if (dx > 0)
 742:       {
 743:         blitPaint.x = portBounds.x + portBounds.width - dx;
 744:         blitPaint.width = dx;
 745:       }
 746:     else if (dx < 0)
 747:       {
 748:         blitPaint.width = -dx;
 749:       }
 750: 
 751:     return true;
 752:   }
 753: 
 754:   /**
 755:    * Paints the viewport in case we have a scrollmode of
 756:    * {@link #SIMPLE_SCROLL_MODE}.
 757:    *
 758:    * This simply paints the view directly on the surface of the viewport.
 759:    *
 760:    * @param g the graphics context to use
 761:    */
 762:   void paintSimple(Graphics g)
 763:   {
 764:     Point pos = getViewPosition();
 765:     Component view = getView();
 766:     boolean translated = false;
 767:     try
 768:       {
 769:         g.translate(-pos.x, -pos.y);
 770:         translated = true;
 771:         view.paint(g);
 772:       } 
 773:     finally
 774:       {
 775:         if (translated)
 776:           g.translate (pos.x, pos.y);
 777:       }
 778:   }
 779: 
 780:   /**
 781:    * Paints the viewport in case we have a scroll mode of
 782:    * {@link #BACKINGSTORE_SCROLL_MODE}.
 783:    *
 784:    * This method uses a backing store image to paint the view to, which is then
 785:    * subsequently painted on the screen. This should make scrolling more
 786:    * smooth.
 787:    *
 788:    * @param g the graphics context to use
 789:    */
 790:   void paintBackingStore(Graphics g)
 791:   {
 792:     // If we have no backing store image yet or the size of the component has
 793:     // changed, we need to rebuild the backing store.
 794:     if (backingStoreImage == null || sizeChanged)
 795:       {
 796:         backingStoreImage = createImage(getWidth(), getHeight());
 797:         sizeChanged = false;
 798:         Graphics g2 = backingStoreImage.getGraphics();
 799:         paintSimple(g2);
 800:         g2.dispose();
 801:       }
 802:     // Otherwise we can perform the blitting on the backing store image:
 803:     // First we move the part that remains visible after scrolling, then
 804:     // we only need to paint the bit that becomes newly visible.
 805:     else
 806:       {
 807:         Graphics g2 = backingStoreImage.getGraphics();
 808:         Point viewPosition = getViewPosition();
 809:         int dx = viewPosition.x - lastPaintPosition.x;
 810:         int dy = viewPosition.y - lastPaintPosition.y;
 811:         boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo,
 812:                                       cachedBlitSize, cachedBlitPaint);
 813:         if (canBlit)
 814:           {
 815:             // Copy the part that remains visible during scrolling.
 816:             g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y,
 817:                         cachedBlitSize.width, cachedBlitSize.height,
 818:                         cachedBlitTo.x - cachedBlitFrom.x,
 819:                         cachedBlitTo.y - cachedBlitFrom.y);
 820:             // Now paint the part that becomes newly visible.
 821:             g2.setClip(cachedBlitPaint.x, cachedBlitPaint.y,
 822:                        cachedBlitPaint.width, cachedBlitPaint.height);
 823:             paintSimple(g2);
 824:           }
 825:         // If blitting is not possible for some reason, fall back to repainting
 826:         // everything.
 827:         else
 828:           {
 829:             paintSimple(g2);
 830:           }
 831:         g2.dispose();
 832:       }
 833:     // Actually draw the backingstore image to the graphics context.
 834:     g.drawImage(backingStoreImage, 0, 0, this);
 835:     // Update the lastPaintPosition so that we know what is already drawn when
 836:     // we paint the next time.
 837:     lastPaintPosition.setLocation(getViewPosition());
 838:   }
 839: 
 840:   /**
 841:    * Paints the viewport in case we have a scrollmode of
 842:    * {@link #BLIT_SCROLL_MODE}.
 843:    *
 844:    * This paints the viewport using a backingstore and a blitting algorithm.
 845:    * Only the newly exposed area of the view is painted from the view painting
 846:    * methods, the remainder is copied from the backing store.
 847:    *
 848:    * @param g the graphics context to use
 849:    */
 850:   void paintBlit(Graphics g)
 851:   {
 852:     // We cannot perform blitted painting as it is described in Sun's API docs.
 853:     // There it is suggested that this painting method should blit directly
 854:     // on the parent window's surface. This is not possible because when using
 855:     // Swing's double buffering (at least our implementation), it would
 856:     // immediatly be painted when the buffer is painted on the screen. For this
 857:     // to work we would need a kind of hole in the buffer image. And honestly
 858:     // I find this method not very elegant.
 859:     // The alternative, blitting directly on the buffer image, is also not
 860:     // possible because the buffer image gets cleared everytime when an opaque
 861:     // parent component is drawn on it.
 862: 
 863:     // What we do instead is falling back to the backing store approach which
 864:     // is in fact a mixed blitting/backing store approach where the blitting
 865:     // is performed on the backing store image and this is then drawn to the
 866:     // graphics context. This is very robust and works independent of the
 867:     // painting mechanism that is used by Swing. And it should have comparable
 868:     // performance characteristics as the blitting method.
 869:     paintBackingStore(g);
 870:   }
 871: }