Source for javax.swing.RepaintManager

   1: /* RepaintManager.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.applet.Applet;
  42: import java.awt.Component;
  43: import java.awt.Dimension;
  44: import java.awt.Graphics;
  45: import java.awt.Image;
  46: import java.awt.Rectangle;
  47: import java.awt.Window;
  48: import java.awt.image.VolatileImage;
  49: import java.util.ArrayList;
  50: import java.util.HashMap;
  51: import java.util.HashSet;
  52: import java.util.Iterator;
  53: import java.util.Map;
  54: import java.util.Set;
  55: import java.util.WeakHashMap;
  56: 
  57: /**
  58:  * <p>The repaint manager holds a set of dirty regions, invalid components,
  59:  * and a double buffer surface.  The dirty regions and invalid components
  60:  * are used to coalesce multiple revalidate() and repaint() calls in the
  61:  * component tree into larger groups to be refreshed "all at once"; the
  62:  * double buffer surface is used by root components to paint
  63:  * themselves.</p>
  64:  *
  65:  * <p>See <a
  66:  * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
  67:  * document</a> for more details.</p>
  68:  *
  69:  * @author Roman Kennke (kennke@aicas.com)
  70:  * @author Graydon Hoare (graydon@redhat.com)
  71:  * @author Audrius Meskauskas (audriusa@bioinformatics.org)
  72:  */
  73: public class RepaintManager
  74: {
  75:   /**
  76:    * The current repaint managers, indexed by their ThreadGroups.
  77:    */
  78:   static WeakHashMap currentRepaintManagers;
  79: 
  80:   /**
  81:    * A rectangle object to be reused in damaged regions calculation.
  82:    */
  83:   private static Rectangle rectCache = new Rectangle();
  84: 
  85:   /**
  86:    * <p>A helper class which is placed into the system event queue at
  87:    * various times in order to facilitate repainting and layout. There is
  88:    * typically only one of these objects active at any time. When the
  89:    * {@link RepaintManager} is told to queue a repaint, it checks to see if
  90:    * a {@link RepaintWorker} is "live" in the system event queue, and if
  91:    * not it inserts one using {@link SwingUtilities#invokeLater}.</p>
  92:    *
  93:    * <p>When the {@link RepaintWorker} comes to the head of the system
  94:    * event queue, its {@link RepaintWorker#run} method is executed by the
  95:    * swing paint thread, which revalidates all invalid components and
  96:    * repaints any damage in the swing scene.</p>
  97:    */
  98:   private class RepaintWorker
  99:     implements Runnable
 100:   {
 101: 
 102:     boolean live;
 103: 
 104:     public RepaintWorker()
 105:     {
 106:       live = false;
 107:     }
 108: 
 109:     public synchronized void setLive(boolean b) 
 110:     {
 111:       live = b;
 112:     }
 113: 
 114:     public synchronized boolean isLive()
 115:     {
 116:       return live;
 117:     }
 118: 
 119:     public void run()
 120:     {
 121:       try
 122:         {
 123:           ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 124:           RepaintManager rm =
 125:             (RepaintManager) currentRepaintManagers.get(threadGroup);
 126:           rm.validateInvalidComponents();
 127:           rm.paintDirtyRegions();
 128:         }
 129:       finally
 130:         {
 131:           setLive(false);
 132:         }
 133:     }
 134: 
 135:   }
 136: 
 137:   /** 
 138:    * A table storing the dirty regions of components.  The keys of this
 139:    * table are components, the values are rectangles. Each component maps
 140:    * to exactly one rectangle.  When more regions are marked as dirty on a
 141:    * component, they are union'ed with the existing rectangle.
 142:    *
 143:    * This is package private to avoid a synthetic accessor method in inner
 144:    * class.
 145:    *
 146:    * @see #addDirtyRegion
 147:    * @see #getDirtyRegion
 148:    * @see #isCompletelyDirty
 149:    * @see #markCompletelyClean
 150:    * @see #markCompletelyDirty
 151:    */
 152:   private HashMap dirtyComponents;
 153: 
 154:   /**
 155:    * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary
 156:    * locking.
 157:    */
 158:   private HashMap dirtyComponentsWork;
 159: 
 160:   /**
 161:    * A single, shared instance of the helper class. Any methods which mark
 162:    * components as invalid or dirty eventually activate this instance. It
 163:    * is added to the event queue if it is not already active, otherwise
 164:    * reused.
 165:    *
 166:    * @see #addDirtyRegion
 167:    * @see #addInvalidComponent
 168:    */
 169:   private RepaintWorker repaintWorker;
 170: 
 171:   /** 
 172:    * The set of components which need revalidation, in the "layout" sense.
 173:    * There is no additional information about "what kind of layout" they
 174:    * need (as there is with dirty regions), so it is just a vector rather
 175:    * than a table.
 176:    *
 177:    * @see #addInvalidComponent
 178:    * @see #removeInvalidComponent
 179:    * @see #validateInvalidComponents
 180:    */
 181:   private ArrayList invalidComponents;
 182: 
 183:   /** 
 184:    * Whether or not double buffering is enabled on this repaint
 185:    * manager. This is merely a hint to clients; the RepaintManager will
 186:    * always return an offscreen buffer when one is requested.
 187:    * 
 188:    * @see #isDoubleBufferingEnabled
 189:    * @see #setDoubleBufferingEnabled
 190:    */
 191:   private boolean doubleBufferingEnabled;
 192: 
 193:   /**
 194:    * The offscreen buffers. This map holds one offscreen buffer per
 195:    * Window/Applet and releases them as soon as the Window/Applet gets garbage
 196:    * collected.
 197:    */
 198:   private WeakHashMap offscreenBuffers;
 199: 
 200:   /**
 201:    * Indicates if the RepaintManager is currently repainting an area.
 202:    */
 203:   private boolean repaintUnderway;
 204: 
 205:   /**
 206:    * This holds buffer commit requests when the RepaintManager is working.
 207:    * This maps Component objects (the top level components) to Rectangle
 208:    * objects (the area of the corresponding buffer that must be blitted on
 209:    * the component).
 210:    */
 211:   private HashMap commitRequests;
 212: 
 213:   /**
 214:    * The maximum width and height to allocate as a double buffer. Requests
 215:    * beyond this size are ignored.
 216:    *
 217:    * @see #paintDirtyRegions
 218:    * @see #getDoubleBufferMaximumSize
 219:    * @see #setDoubleBufferMaximumSize
 220:    */
 221:   private Dimension doubleBufferMaximumSize;
 222: 
 223: 
 224:   /**
 225:    * Create a new RepaintManager object.
 226:    */
 227:   public RepaintManager()
 228:   {
 229:     dirtyComponents = new HashMap();
 230:     dirtyComponentsWork = new HashMap();
 231:     invalidComponents = new ArrayList();
 232:     repaintWorker = new RepaintWorker();
 233:     doubleBufferMaximumSize = new Dimension(2000,2000);
 234:     doubleBufferingEnabled = true;
 235:     offscreenBuffers = new WeakHashMap();
 236:     repaintUnderway = false;
 237:     commitRequests = new HashMap();
 238:   }
 239: 
 240:   /**
 241:    * Returns the <code>RepaintManager</code> for the current thread's
 242:    * thread group. The default implementation ignores the
 243:    * <code>component</code> parameter and returns the same repaint manager
 244:    * for all components.
 245:    *
 246:    * @param component a component to look up the manager of
 247:    *
 248:    * @return the current repaint manager for the calling thread's thread group
 249:    *         and the specified component
 250:    *
 251:    * @see #setCurrentManager
 252:    */
 253:   public static RepaintManager currentManager(Component component)
 254:   {
 255:     if (currentRepaintManagers == null)
 256:       currentRepaintManagers = new WeakHashMap();
 257:     ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 258:     RepaintManager currentManager =
 259:       (RepaintManager) currentRepaintManagers.get(threadGroup);
 260:     if (currentManager == null)
 261:       {
 262:         currentManager = new RepaintManager();
 263:         currentRepaintManagers.put(threadGroup, currentManager);
 264:       }
 265:     return currentManager;
 266:   }
 267: 
 268:   /**
 269:    * Returns the <code>RepaintManager</code> for the current thread's
 270:    * thread group. The default implementation ignores the
 271:    * <code>component</code> parameter and returns the same repaint manager
 272:    * for all components.
 273:    *
 274:    * This method is only here for backwards compatibility with older versions
 275:    * of Swing and simply forwards to {@link #currentManager(Component)}.
 276:    *
 277:    * @param component a component to look up the manager of
 278:    *
 279:    * @return the current repaint manager for the calling thread's thread group
 280:    *         and the specified component
 281:    *
 282:    * @see #setCurrentManager
 283:    */
 284:   public static RepaintManager currentManager(JComponent component)
 285:   {
 286:     return currentManager((Component)component);
 287:   }
 288: 
 289:   /**
 290:    * Sets the repaint manager for the calling thread's thread group.
 291:    *
 292:    * @param manager the repaint manager to set for the current thread's thread
 293:    *        group
 294:    *
 295:    * @see #currentManager(Component)
 296:    */
 297:   public static void setCurrentManager(RepaintManager manager)
 298:   {
 299:     if (currentRepaintManagers == null)
 300:       currentRepaintManagers = new WeakHashMap();
 301: 
 302:     ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 303:     currentRepaintManagers.put(threadGroup, manager);
 304:   }
 305: 
 306:   /**
 307:    * Add a component to the {@link #invalidComponents} vector. If the
 308:    * {@link #repaintWorker} class is not active, insert it in the system
 309:    * event queue.
 310:    *
 311:    * @param component The component to add
 312:    *
 313:    * @see #removeInvalidComponent
 314:    */
 315:   public void addInvalidComponent(JComponent component)
 316:   {
 317:     Component ancestor = component;
 318: 
 319:     while (ancestor != null
 320:            && (! (ancestor instanceof JComponent)
 321:                || ! ((JComponent) ancestor).isValidateRoot() ))
 322:       ancestor = ancestor.getParent();
 323: 
 324:     if (ancestor != null
 325:         && ancestor instanceof JComponent
 326:         && ((JComponent) ancestor).isValidateRoot())
 327:       component = (JComponent) ancestor;
 328: 
 329:     if (invalidComponents.contains(component))
 330:       return;
 331: 
 332:     synchronized (invalidComponents)
 333:       {
 334:         invalidComponents.add(component);
 335:       }
 336: 
 337:     if (! repaintWorker.isLive())
 338:       {
 339:         repaintWorker.setLive(true);
 340:         SwingUtilities.invokeLater(repaintWorker);
 341:       }
 342:   }
 343: 
 344:   /**
 345:    * Remove a component from the {@link #invalidComponents} vector.
 346:    *
 347:    * @param component The component to remove
 348:    *
 349:    * @see #addInvalidComponent
 350:    */
 351:   public void removeInvalidComponent(JComponent component)
 352:   {
 353:     synchronized (invalidComponents)
 354:       {
 355:         invalidComponents.remove(component);
 356:       }
 357:   }
 358: 
 359:   /**
 360:    * Add a region to the set of dirty regions for a specified component.
 361:    * This involves union'ing the new region with any existing dirty region
 362:    * associated with the component. If the {@link #repaintWorker} class
 363:    * is not active, insert it in the system event queue.
 364:    *
 365:    * @param component The component to add a dirty region for
 366:    * @param x The left x coordinate of the new dirty region
 367:    * @param y The top y coordinate of the new dirty region
 368:    * @param w The width of the new dirty region
 369:    * @param h The height of the new dirty region
 370:    *
 371:    * @see #addDirtyRegion
 372:    * @see #getDirtyRegion
 373:    * @see #isCompletelyDirty
 374:    * @see #markCompletelyClean
 375:    * @see #markCompletelyDirty
 376:    */
 377:   public void addDirtyRegion(JComponent component, int x, int y,
 378:                              int w, int h)
 379:   {
 380:     if (w <= 0 || h <= 0 || !component.isShowing())
 381:       return;
 382:     
 383:     Component parent = component.getParent();
 384:     
 385:     component.computeVisibleRect(rectCache);
 386:     SwingUtilities.computeIntersection(x, y, w, h, rectCache);
 387: 
 388:     if (! rectCache.isEmpty())
 389:       {
 390:         if (dirtyComponents.containsKey(component))
 391:           {
 392:             SwingUtilities.computeUnion(rectCache.x, rectCache.y,
 393:                                         rectCache.width, rectCache.height,
 394:                                    (Rectangle) dirtyComponents.get(component));
 395:           }
 396:         else
 397:           {
 398:             synchronized (dirtyComponents)
 399:               {
 400:                 dirtyComponents.put(component, rectCache.getBounds());
 401:               }
 402:           }
 403: 
 404:         if (! repaintWorker.isLive())
 405:           {
 406:             repaintWorker.setLive(true);
 407:             SwingUtilities.invokeLater(repaintWorker);
 408:           }
 409:       }
 410:   }
 411: 
 412:   /**
 413:    * Get the dirty region associated with a component, or <code>null</code>
 414:    * if the component has no dirty region.
 415:    *
 416:    * @param component The component to get the dirty region of
 417:    *
 418:    * @return The dirty region of the component
 419:    *
 420:    * @see #dirtyComponents
 421:    * @see #addDirtyRegion
 422:    * @see #isCompletelyDirty
 423:    * @see #markCompletelyClean
 424:    * @see #markCompletelyDirty
 425:    */
 426:   public Rectangle getDirtyRegion(JComponent component)
 427:   {
 428:     Rectangle dirty = (Rectangle) dirtyComponents.get(component);
 429:     if (dirty == null)
 430:       dirty = new Rectangle();
 431:     return dirty;
 432:   }
 433:   
 434:   /**
 435:    * Mark a component as dirty over its entire bounds.
 436:    *
 437:    * @param component The component to mark as dirty
 438:    *
 439:    * @see #dirtyComponents
 440:    * @see #addDirtyRegion
 441:    * @see #getDirtyRegion
 442:    * @see #isCompletelyDirty
 443:    * @see #markCompletelyClean
 444:    */
 445:   public void markCompletelyDirty(JComponent component)
 446:   {
 447:     Rectangle r = component.getBounds();
 448:     addDirtyRegion(component, 0, 0, r.width, r.height);
 449:   }
 450: 
 451:   /**
 452:    * Remove all dirty regions for a specified component
 453:    *
 454:    * @param component The component to mark as clean
 455:    *
 456:    * @see #dirtyComponents
 457:    * @see #addDirtyRegion
 458:    * @see #getDirtyRegion
 459:    * @see #isCompletelyDirty
 460:    * @see #markCompletelyDirty
 461:    */
 462:   public void markCompletelyClean(JComponent component)
 463:   {
 464:     synchronized (dirtyComponents)
 465:       {
 466:         dirtyComponents.remove(component);
 467:       }
 468:   }
 469: 
 470:   /**
 471:    * Return <code>true</code> if the specified component is completely
 472:    * contained within its dirty region, otherwise <code>false</code>
 473:    *
 474:    * @param component The component to check for complete dirtyness
 475:    *
 476:    * @return Whether the component is completely dirty
 477:    *
 478:    * @see #dirtyComponents
 479:    * @see #addDirtyRegion
 480:    * @see #getDirtyRegion
 481:    * @see #isCompletelyDirty
 482:    * @see #markCompletelyClean
 483:    */
 484:   public boolean isCompletelyDirty(JComponent component)
 485:   {
 486:     boolean retVal = false;
 487:     if (dirtyComponents.containsKey(component))
 488:       {
 489:         Rectangle dirtyRegion = (Rectangle) dirtyComponents.get(component);
 490:         retVal = dirtyRegion.equals(SwingUtilities.getLocalBounds(component));
 491:       }
 492:     return retVal;
 493:   }
 494: 
 495:   /**
 496:    * Validate all components which have been marked invalid in the {@link
 497:    * #invalidComponents} vector.
 498:    */
 499:   public void validateInvalidComponents()
 500:   {
 501:     // We don't use an iterator here because that would fail when there are
 502:     // components invalidated during the validation of others, which happens
 503:     // quite frequently. Instead we synchronize the access a little more.
 504:     while (invalidComponents.size() > 0)
 505:       {
 506:         Component comp;
 507:         synchronized (invalidComponents)
 508:           {
 509:             comp = (Component) invalidComponents.remove(0);
 510:           }
 511:         // Validate the validate component.
 512:         if (! (comp.isVisible() && comp.isShowing()))
 513:           continue;
 514:         comp.validate();
 515:       }
 516:   }
 517: 
 518:   /**
 519:    * Repaint all regions of all components which have been marked dirty in the
 520:    * {@link #dirtyComponents} table.
 521:    */
 522:   public void paintDirtyRegions()
 523:   {
 524:     // Short cicuit if there is nothing to paint.
 525:     if (dirtyComponents.size() == 0)
 526:       return;
 527: 
 528:     // Swap dirtyRegions with dirtyRegionsWork to avoid locking.
 529:     synchronized (dirtyComponents)
 530:       {
 531:         HashMap swap = dirtyComponents;
 532:         dirtyComponents = dirtyComponentsWork;
 533:         dirtyComponentsWork = swap;
 534:       }
 535: 
 536:     // Compile a set of repaint roots.
 537:     HashSet repaintRoots = new HashSet();
 538:     Set components = dirtyComponentsWork.keySet();
 539:     for (Iterator i = components.iterator(); i.hasNext();)
 540:       {
 541:         JComponent dirty = (JComponent) i.next();
 542:         compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots);
 543:       }
 544: 
 545:     repaintUnderway = true;
 546:     for (Iterator i = repaintRoots.iterator(); i.hasNext();)
 547:       {
 548:         JComponent comp = (JComponent) i.next();
 549:         Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp);
 550:         if (damaged == null || damaged.isEmpty())
 551:           continue;
 552:         comp.paintImmediately(damaged);
 553:       }
 554:     dirtyComponentsWork.clear();
 555:     repaintUnderway = false;
 556:     commitRemainingBuffers();
 557:   }
 558:   
 559:   /**
 560:    * Compiles a list of components that really get repainted. This is called
 561:    * once for each component in the dirtyComponents HashMap, each time with
 562:    * another <code>dirty</code> parameter. This searches up the component
 563:    * hierarchy of <code>dirty</code> to find the highest parent that is also
 564:    * marked dirty and merges the dirty regions.
 565:    *
 566:    * @param dirtyRegions the dirty regions 
 567:    * @param dirty the component for which to find the repaint root
 568:    * @param roots the list to which new repaint roots get appended
 569:    */
 570:   private void compileRepaintRoots(HashMap dirtyRegions, JComponent dirty,
 571:                                    HashSet roots)
 572:   {
 573:     Component current = dirty;
 574:     Component root = dirty;
 575: 
 576:     // Search the highest component that is also marked dirty.
 577:     Component parent;
 578:     while (true)
 579:       {
 580:         parent = current.getParent();
 581:         if (parent == null || !(parent instanceof JComponent))
 582:           break;
 583: 
 584:         current = parent;
 585:         // We can skip to the next up when this parent is not dirty.
 586:         if (dirtyRegions.containsKey(parent))
 587:           {
 588:             root = current;
 589:           }
 590:       }
 591: 
 592:     // Merge the rectangles of the root and the requested component if
 593:     // the are different.
 594:     if (root != dirty)
 595:       {
 596:         Rectangle dirtyRect = (Rectangle) dirtyRegions.get(dirty);
 597:         dirtyRect = SwingUtilities.convertRectangle(dirty, dirtyRect, root);
 598:         Rectangle rootRect = (Rectangle) dirtyRegions.get(root);
 599:         SwingUtilities.computeUnion(dirtyRect.x, dirtyRect.y, dirtyRect.width,
 600:                                     dirtyRect.height, rootRect);
 601:       }
 602: 
 603:     // Adds the root to the roots set.
 604:     roots.add(root);
 605:   }
 606: 
 607:   /**
 608:    * Get an offscreen buffer for painting a component's image. This image
 609:    * may be smaller than the proposed dimensions, depending on the value of
 610:    * the {@link #doubleBufferMaximumSize} property.
 611:    *
 612:    * @param component The component to return an offscreen buffer for
 613:    * @param proposedWidth The proposed width of the offscreen buffer
 614:    * @param proposedHeight The proposed height of the offscreen buffer
 615:    *
 616:    * @return A shared offscreen buffer for painting
 617:    */
 618:   public Image getOffscreenBuffer(Component component, int proposedWidth,
 619:                                   int proposedHeight)
 620:   {
 621:     Component root = getRoot(component);
 622:     Image buffer = (Image) offscreenBuffers.get(root);
 623:     if (buffer == null 
 624:         || buffer.getWidth(null) < proposedWidth 
 625:         || buffer.getHeight(null) < proposedHeight)
 626:       {
 627:         int width = Math.max(proposedWidth, root.getWidth());
 628:         width = Math.min(doubleBufferMaximumSize.width, width);
 629:         int height = Math.max(proposedHeight, root.getHeight());
 630:         height = Math.min(doubleBufferMaximumSize.height, height);
 631:         buffer = component.createImage(width, height);
 632:         offscreenBuffers.put(root, buffer);
 633:       }
 634:     return buffer;
 635:   }
 636:   
 637:   /**
 638:    * Gets the root of the component given. If a parent of the 
 639:    * component is an instance of Applet, then the applet is 
 640:    * returned. The applet is considered the root for painting.
 641:    * Otherwise, the root Window is returned if it exists.
 642:    * 
 643:    * @param comp - The component to get the root for.
 644:    * @return the parent root. An applet if it is a parent,
 645:    * or the root window. If neither exist, null is returned.
 646:    */
 647:   private Component getRoot(Component comp)
 648:   {
 649:       Applet app = null;
 650:       
 651:       while (comp != null)
 652:         {
 653:           if (app == null && comp instanceof Window)
 654:             return comp;
 655:           else if (comp instanceof Applet)
 656:             app = (Applet) comp;
 657:           comp = comp.getParent();
 658:         }
 659:       
 660:       return app;
 661:   }
 662:   
 663:   /**
 664:    * Blits the back buffer of the specified root component to the screen. If
 665:    * the RepaintManager is currently working on a paint request, the commit
 666:    * requests are queued up and committed at once when the paint request is
 667:    * done (by {@link #commitRemainingBuffers}). This is package private because
 668:    * it must get called by JComponent.
 669:    *
 670:    * @param root the component, either a Window or an Applet instance
 671:    * @param area the area to paint on screen
 672:    */
 673:   void commitBuffer(Component root, Rectangle area)
 674:   {
 675:     // We synchronize on dirtyComponents here because that is what
 676:     // paintDirtyRegions also synchronizes on while painting.
 677:     synchronized (dirtyComponents)
 678:       {
 679:         // If the RepaintManager is not currently painting, then directly
 680:         // blit the requested buffer on the screen.
 681:         if (! repaintUnderway)
 682:           {
 683:             Graphics g = root.getGraphics();
 684:             Image buffer = (Image) offscreenBuffers.get(root);
 685:             Rectangle clip = g.getClipBounds();
 686:             if (clip != null)
 687:               area = SwingUtilities.computeIntersection(clip.x, clip.y,
 688:                                                         clip.width, clip.height,
 689:                                                         area);
 690:             int dx1 = area.x;
 691:             int dy1 = area.y;
 692:             int dx2 = area.x + area.width;
 693:             int dy2 = area.y + area.height;
 694:             // Make sure we have a sane clip at this point.
 695:             g.clipRect(area.x, area.y, area.width, area.height);
 696: 
 697:             // Make sure the coordinates are inside the buffer, everything else
 698:             // might lead to problems.
 699:             // TODO: This code should not really be necessary, however, in fact
 700:             // we have two issues here:
 701:             // 1. We shouldn't get repaint requests in areas outside the buffer
 702:             //    region in the first place. This still happens for example
 703:             //    when a component is inside a JViewport, and the component has
 704:             //    a size that would reach beyond the window size.
 705:             // 2. Graphics.drawImage() should not behave strange when trying
 706:             //    to draw regions outside the image.
 707:             int bufferWidth = buffer.getWidth(root);
 708:             int bufferHeight = buffer.getHeight(root);
 709:             dx1 = Math.min(bufferWidth, dx1);
 710:             dy1 = Math.min(bufferHeight, dy1);
 711:             dx2 = Math.min(bufferWidth, dx2);
 712:             dy2 = Math.min(bufferHeight, dy2);
 713:             g.drawImage(buffer, 0, 0, root);
 714:             g.dispose();
 715:           }
 716:         // Otherwise queue this request up, until all the RepaintManager work
 717:         // is done.
 718:         else
 719:           {
 720:             if (commitRequests.containsKey(root))
 721:               SwingUtilities.computeUnion(area.x, area.y, area.width,
 722:                                           area.height,
 723:                                          (Rectangle) commitRequests.get(root));
 724:             else
 725:               commitRequests.put(root, area);
 726:           }
 727:       }
 728:   }
 729: 
 730:   /**
 731:    * Commits the queued up back buffers to screen all at once.
 732:    */
 733:   private void commitRemainingBuffers()
 734:   {
 735:     // We synchronize on dirtyComponents here because that is what
 736:     // paintDirtyRegions also synchronizes on while painting.
 737:     synchronized (dirtyComponents)
 738:       {
 739:         Set entrySet = commitRequests.entrySet();
 740:         Iterator i = entrySet.iterator();
 741:         while (i.hasNext())
 742:           {
 743:             Map.Entry entry = (Map.Entry) i.next();
 744:             Component root = (Component) entry.getKey();
 745:             Rectangle area = (Rectangle) entry.getValue();
 746:             commitBuffer(root, area);
 747:             i.remove();
 748:           }
 749:       }
 750:   }
 751: 
 752:   /**
 753:    * Creates and returns a volatile offscreen buffer for the specified
 754:    * component that can be used as a double buffer. The returned image
 755:    * is a {@link VolatileImage}. Its size will be <code>(proposedWidth,
 756:    * proposedHeight)</code> except when the maximum double buffer size
 757:    * has been set in this RepaintManager.
 758:    *
 759:    * @param comp the Component for which to create a volatile buffer
 760:    * @param proposedWidth the proposed width of the buffer
 761:    * @param proposedHeight the proposed height of the buffer
 762:    *
 763:    * @since 1.4
 764:    *
 765:    * @see VolatileImage
 766:    */
 767:   public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
 768:                                           int proposedHeight)
 769:   {
 770:     Component root = getRoot(comp);
 771:     Image buffer = (Image) offscreenBuffers.get(root);
 772:     if (buffer == null 
 773:         || buffer.getWidth(null) < proposedWidth 
 774:         || buffer.getHeight(null) < proposedHeight
 775:         || !(buffer instanceof VolatileImage))
 776:       {
 777:         int width = Math.max(proposedWidth, root.getWidth());
 778:         width = Math.min(doubleBufferMaximumSize.width, width);
 779:         int height = Math.max(proposedHeight, root.getHeight());
 780:         height = Math.min(doubleBufferMaximumSize.height, height);
 781:         buffer = root.createVolatileImage(width, height);
 782:         if (buffer != null)
 783:           offscreenBuffers.put(root, buffer);
 784:       }
 785:     return buffer;
 786:   }
 787:   
 788: 
 789:   /**
 790:    * Get the value of the {@link #doubleBufferMaximumSize} property.
 791:    *
 792:    * @return The current value of the property
 793:    *
 794:    * @see #setDoubleBufferMaximumSize
 795:    */
 796:   public Dimension getDoubleBufferMaximumSize()
 797:   {
 798:     return doubleBufferMaximumSize;
 799:   }
 800: 
 801:   /**
 802:    * Set the value of the {@link #doubleBufferMaximumSize} property.
 803:    *
 804:    * @param size The new value of the property
 805:    *
 806:    * @see #getDoubleBufferMaximumSize
 807:    */
 808:   public void setDoubleBufferMaximumSize(Dimension size)
 809:   {
 810:     doubleBufferMaximumSize = size;
 811:   }
 812: 
 813:   /**
 814:    * Set the value of the {@link #doubleBufferingEnabled} property.
 815:    *
 816:    * @param buffer The new value of the property
 817:    *
 818:    * @see #isDoubleBufferingEnabled
 819:    */
 820:   public void setDoubleBufferingEnabled(boolean buffer)
 821:   {
 822:     doubleBufferingEnabled = buffer;
 823:   }
 824: 
 825:   /**
 826:    * Get the value of the {@link #doubleBufferingEnabled} property.
 827:    *
 828:    * @return The current value of the property
 829:    *
 830:    * @see #setDoubleBufferingEnabled
 831:    */
 832:   public boolean isDoubleBufferingEnabled()
 833:   {
 834:     return doubleBufferingEnabled;
 835:   }
 836:   
 837:   public String toString()
 838:   {
 839:     return "RepaintManager";
 840:   }
 841: }