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