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