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