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