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.Image;
  44: import java.awt.Rectangle;
  45: import java.awt.image.VolatileImage;
  46: import java.util.ArrayList;
  47: import java.util.Collections;
  48: import java.util.Comparator;
  49: import java.util.HashMap;
  50: import java.util.Iterator;
  51: import java.util.WeakHashMap;
  52: 
  53: /**
  54:  * <p>The repaint manager holds a set of dirty regions, invalid components,
  55:  * and a double buffer surface.  The dirty regions and invalid components
  56:  * are used to coalesce multiple revalidate() and repaint() calls in the
  57:  * component tree into larger groups to be refreshed "all at once"; the
  58:  * double buffer surface is used by root components to paint
  59:  * themselves.</p>
  60:  *
  61:  * <p>In general, painting is very confusing in swing. see <a
  62:  * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
  63:  * document</a> for more details.</p>
  64:  *
  65:  * @author Graydon Hoare (graydon@redhat.com)
  66:  */
  67: public class RepaintManager
  68: {
  69:   /**
  70:    * The current repaint managers, indexed by their ThreadGroups.
  71:    */
  72:   static WeakHashMap currentRepaintManagers;
  73:   
  74:   /**
  75:    * <p>A helper class which is placed into the system event queue at
  76:    * various times in order to facilitate repainting and layout. There is
  77:    * typically only one of these objects active at any time. When the
  78:    * {@link RepaintManager} is told to queue a repaint, it checks to see if
  79:    * a {@link RepaintWorker} is "live" in the system event queue, and if
  80:    * not it inserts one using {@link SwingUtilities#invokeLater}.</p>
  81:    *
  82:    * <p>When the {@link RepaintWorker} comes to the head of the system
  83:    * event queue, its {@link RepaintWorker#run} method is executed by the
  84:    * swing paint thread, which revalidates all invalid components and
  85:    * repaints any damage in the swing scene.</p>
  86:    */
  87:   protected class RepaintWorker
  88:     implements Runnable
  89:   {
  90: 
  91:     boolean live;
  92: 
  93:     public RepaintWorker()
  94:     {
  95:       live = false;
  96:     }
  97: 
  98:     public synchronized void setLive(boolean b) 
  99:     {
 100:       live = b;
 101:     }
 102: 
 103:     public synchronized boolean isLive()
 104:     {
 105:       return live;
 106:     }
 107: 
 108:     public void run()
 109:     {
 110:       ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 111:       RepaintManager rm =
 112:         (RepaintManager) currentRepaintManagers.get(threadGroup);
 113:       setLive(false);
 114:       rm.validateInvalidComponents();
 115:       rm.paintDirtyRegions();
 116:     }
 117: 
 118:   }
 119: 
 120:   /**
 121:    * Compares two components using their depths in the component hierarchy.
 122:    * A component with a lesser depth (higher level components) are sorted
 123:    * before components with a deeper depth (low level components). This is used
 124:    * to order paint requests, so that the higher level components are painted
 125:    * before the low level components get painted.
 126:    *
 127:    * @author Roman Kennke (kennke@aicas.com)
 128:    */
 129:   private class ComponentComparator implements Comparator
 130:   {
 131: 
 132:     /**
 133:      * Compares two components.
 134:      *
 135:      * @param o1 the first component
 136:      * @param o2 the second component
 137:      *
 138:      * @return a negative integer, if <code>o1</code> is higher in the
 139:      *         hierarchy than <code>o2</code>, zero, if both are at the same
 140:      *         level and a positive integer, if <code>o1</code> is deeper in
 141:      *         the hierarchy than <code>o2</code> 
 142:      */
 143:     public int compare(Object o1, Object o2)
 144:     {
 145:       if (o1 instanceof JComponent && o2 instanceof JComponent)
 146:         {
 147:           JComponent c1 = (JComponent) o1;
 148:           JComponent c2 = (JComponent) o2;
 149:           return getDepth(c1) - getDepth(c2);
 150:         }
 151:       else
 152:         throw new ClassCastException("This comparator can only be used with "
 153:                                      + "JComponents");
 154:     }
 155: 
 156:     /**
 157:      * Computes the depth for a given JComponent.
 158:      *
 159:      * @param c the component to compute the depth for
 160:      *
 161:      * @return the depth of the component
 162:      */
 163:     private int getDepth(JComponent c)
 164:     {
 165:       Component comp = c;
 166:       int depth = 0;
 167:       while (comp != null)
 168:         {
 169:           comp = comp.getParent();
 170:           depth++;
 171:         }
 172:       return depth;
 173:     }
 174:   }
 175: 
 176:   /** 
 177:    * A table storing the dirty regions of components.  The keys of this
 178:    * table are components, the values are rectangles. Each component maps
 179:    * to exactly one rectangle.  When more regions are marked as dirty on a
 180:    * component, they are union'ed with the existing rectangle.
 181:    *
 182:    * @see #addDirtyRegion
 183:    * @see #getDirtyRegion
 184:    * @see #isCompletelyDirty
 185:    * @see #markCompletelyClean
 186:    * @see #markCompletelyDirty
 187:    */
 188:   HashMap dirtyComponents;
 189: 
 190:   HashMap workDirtyComponents;
 191: 
 192:   /**
 193:    * Stores the order in which the components get repainted.
 194:    */
 195:   ArrayList repaintOrder;
 196:   ArrayList workRepaintOrder;
 197: 
 198:   /**
 199:    * The comparator used for ordered inserting into the repaintOrder list. 
 200:    */
 201:   Comparator comparator;
 202: 
 203:   /**
 204:    * A single, shared instance of the helper class. Any methods which mark
 205:    * components as invalid or dirty eventually activate this instance. It
 206:    * is added to the event queue if it is not already active, otherwise
 207:    * reused.
 208:    *
 209:    * @see #addDirtyRegion
 210:    * @see #addInvalidComponent
 211:    */
 212:   RepaintWorker repaintWorker;
 213: 
 214:   /** 
 215:    * The set of components which need revalidation, in the "layout" sense.
 216:    * There is no additional information about "what kind of layout" they
 217:    * need (as there is with dirty regions), so it is just a vector rather
 218:    * than a table.
 219:    *
 220:    * @see #addInvalidComponent
 221:    * @see #removeInvalidComponent
 222:    * @see #validateInvalidComponents
 223:    */
 224:   ArrayList invalidComponents;
 225:   ArrayList workInvalidComponents;
 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:   boolean doubleBufferingEnabled;
 236: 
 237:   /** 
 238:    * The current offscreen buffer. This is reused for all requests for
 239:    * offscreen drawing buffers. It grows as necessary, up to {@link
 240:    * #doubleBufferMaximumSize}, but there is only one shared instance.
 241:    *
 242:    * @see #getOffscreenBuffer
 243:    * @see #doubleBufferMaximumSize
 244:    */
 245:   Image doubleBuffer;
 246: 
 247:   /**
 248:    * The maximum width and height to allocate as a double buffer. Requests
 249:    * beyond this size are ignored.
 250:    *
 251:    * @see #paintDirtyRegions
 252:    * @see #getDoubleBufferMaximumSize
 253:    * @see #setDoubleBufferMaximumSize
 254:    */
 255:   Dimension doubleBufferMaximumSize;
 256: 
 257: 
 258:   /**
 259:    * Create a new RepaintManager object.
 260:    */
 261:   public RepaintManager()
 262:   {
 263:     dirtyComponents = new HashMap();
 264:     workDirtyComponents = new HashMap();
 265:     repaintOrder = new ArrayList();
 266:     workRepaintOrder = new ArrayList();
 267:     invalidComponents = new ArrayList();
 268:     workInvalidComponents = new ArrayList();
 269:     repaintWorker = new RepaintWorker();
 270:     doubleBufferMaximumSize = new Dimension(2000,2000);
 271:     doubleBufferingEnabled = true;
 272:   }
 273: 
 274:   /**
 275:    * Returns the <code>RepaintManager</code> for the current thread's
 276:    * thread group. The default implementation ignores the
 277:    * <code>component</code> parameter and returns the same repaint manager
 278:    * for all components.
 279:    *
 280:    * @param component a component to look up the manager of
 281:    *
 282:    * @return the current repaint manager for the calling thread's thread group
 283:    *         and the specified component
 284:    *
 285:    * @see #setCurrentManager
 286:    */
 287:   public static RepaintManager currentManager(Component component)
 288:   {
 289:     if (currentRepaintManagers == null)
 290:       currentRepaintManagers = new WeakHashMap();
 291:     ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 292:     RepaintManager currentManager =
 293:       (RepaintManager) currentRepaintManagers.get(threadGroup);
 294:     if (currentManager == null)
 295:       {
 296:         currentManager = new RepaintManager();
 297:         currentRepaintManagers.put(threadGroup, currentManager);
 298:       }
 299:     return currentManager;
 300:   }
 301: 
 302:   /**
 303:    * Returns the <code>RepaintManager</code> for the current thread's
 304:    * thread group. The default implementation ignores the
 305:    * <code>component</code> parameter and returns the same repaint manager
 306:    * for all components.
 307:    *
 308:    * This method is only here for backwards compatibility with older versions
 309:    * of Swing and simply forwards to {@link #currentManager(Component)}.
 310:    *
 311:    * @param component a component to look up the manager of
 312:    *
 313:    * @return the current repaint manager for the calling thread's thread group
 314:    *         and the specified component
 315:    *
 316:    * @see #setCurrentManager
 317:    */
 318:   public static RepaintManager currentManager(JComponent component)
 319:   {
 320:     return currentManager((Component)component);
 321:   }
 322: 
 323:   /**
 324:    * Sets the repaint manager for the calling thread's thread group.
 325:    *
 326:    * @param manager the repaint manager to set for the current thread's thread
 327:    *        group
 328:    *
 329:    * @see #currentManager(Component)
 330:    */
 331:   public static void setCurrentManager(RepaintManager manager)
 332:   {
 333:     if (currentRepaintManagers == null)
 334:       currentRepaintManagers = new WeakHashMap();
 335: 
 336:     ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 337:     currentRepaintManagers.put(threadGroup, manager);
 338:   }
 339: 
 340:   /**
 341:    * Add a component to the {@link #invalidComponents} vector. If the
 342:    * {@link #repaintWorker} class is not active, insert it in the system
 343:    * event queue.
 344:    *
 345:    * @param component The component to add
 346:    *
 347:    * @see #removeInvalidComponent
 348:    */
 349:   public synchronized void addInvalidComponent(JComponent component)
 350:   {
 351:     Component ancestor = component.getParent();
 352: 
 353:     while (ancestor != null
 354:            && (! (ancestor instanceof JComponent)
 355:                || ! ((JComponent) ancestor).isValidateRoot() ))
 356:       ancestor = ancestor.getParent();
 357: 
 358:     if (ancestor != null
 359:         && ancestor instanceof JComponent
 360:         && ((JComponent) ancestor).isValidateRoot())
 361:       component = (JComponent) ancestor;
 362: 
 363:     if (invalidComponents.contains(component))
 364:       return;
 365: 
 366:     invalidComponents.add(component);
 367:     
 368:     if (! repaintWorker.isLive())
 369:       {
 370:         repaintWorker.setLive(true);
 371:         SwingUtilities.invokeLater(repaintWorker);
 372:       }
 373:   }
 374: 
 375:   /**
 376:    * Remove a component from the {@link #invalidComponents} vector.
 377:    *
 378:    * @param component The component to remove
 379:    *
 380:    * @see #addInvalidComponent
 381:    */
 382:   public synchronized void removeInvalidComponent(JComponent component)
 383:   {
 384:     invalidComponents.remove(component);
 385:   }
 386: 
 387:   /**
 388:    * Add a region to the set of dirty regions for a specified component.
 389:    * This involves union'ing the new region with any existing dirty region
 390:    * associated with the component. If the {@link #repaintWorker} class
 391:    * is not active, insert it in the system event queue.
 392:    *
 393:    * @param component The component to add a dirty region for
 394:    * @param x The left x coordinate of the new dirty region
 395:    * @param y The top y coordinate of the new dirty region
 396:    * @param w The width of the new dirty region
 397:    * @param h The height of the new dirty region
 398:    *
 399:    * @see #addDirtyRegion
 400:    * @see #getDirtyRegion
 401:    * @see #isCompletelyDirty
 402:    * @see #markCompletelyClean
 403:    * @see #markCompletelyDirty
 404:    */
 405:   public synchronized void addDirtyRegion(JComponent component, int x, int y,
 406:                                           int w, int h)
 407:   {
 408:     if (w == 0 || h == 0 || !component.isShowing())
 409:       return;
 410:     Rectangle r = new Rectangle(x, y, w, h);
 411:     if (dirtyComponents.containsKey(component))
 412:       r = r.union((Rectangle)dirtyComponents.get(component));
 413:     else
 414:       insertInRepaintOrder(component);
 415:     dirtyComponents.put(component, r);
 416:     if (! repaintWorker.isLive())
 417:       {
 418:         repaintWorker.setLive(true);
 419:         SwingUtilities.invokeLater(repaintWorker);
 420:       }
 421:   }
 422: 
 423:   /**
 424:    * Inserts a component into the repaintOrder list in an ordered fashion,
 425:    * using a binary search.
 426:    *
 427:    * @param c the component to be inserted
 428:    */
 429:   private void insertInRepaintOrder(JComponent c)
 430:   {
 431:     if (comparator == null)
 432:       comparator = new ComponentComparator();
 433:     int insertIndex = Collections.binarySearch(repaintOrder, c, comparator);
 434:     if (insertIndex < 0)
 435:       insertIndex = -(insertIndex + 1);
 436:     repaintOrder.add(insertIndex, c);
 437:   }
 438: 
 439:   /**
 440:    * Get the dirty region associated with a component, or <code>null</code>
 441:    * if the component has no dirty region.
 442:    *
 443:    * @param component The component to get the dirty region of
 444:    *
 445:    * @return The dirty region of the component
 446:    *
 447:    * @see #dirtyComponents
 448:    * @see #addDirtyRegion
 449:    * @see #isCompletelyDirty
 450:    * @see #markCompletelyClean
 451:    * @see #markCompletelyDirty
 452:    */
 453:   public Rectangle getDirtyRegion(JComponent component)
 454:   {
 455:     Rectangle dirty = (Rectangle) dirtyComponents.get(component);
 456:     if (dirty == null)
 457:       dirty = new Rectangle();
 458:     return dirty;
 459:   }
 460:   
 461:   /**
 462:    * Mark a component as dirty over its entire bounds.
 463:    *
 464:    * @param component The component to mark as dirty
 465:    *
 466:    * @see #dirtyComponents
 467:    * @see #addDirtyRegion
 468:    * @see #getDirtyRegion
 469:    * @see #isCompletelyDirty
 470:    * @see #markCompletelyClean
 471:    */
 472:   public void markCompletelyDirty(JComponent component)
 473:   {
 474:     Rectangle r = component.getBounds();
 475:     addDirtyRegion(component, r.x, r.y, r.width, r.height);
 476:     component.isCompletelyDirty = true;
 477:   }
 478: 
 479:   /**
 480:    * Remove all dirty regions for a specified component
 481:    *
 482:    * @param component The component to mark as clean
 483:    *
 484:    * @see #dirtyComponents
 485:    * @see #addDirtyRegion
 486:    * @see #getDirtyRegion
 487:    * @see #isCompletelyDirty
 488:    * @see #markCompletelyDirty
 489:    */
 490:   public void markCompletelyClean(JComponent component)
 491:   {
 492:     synchronized (this)
 493:       {
 494:         dirtyComponents.remove(component);
 495:       }
 496:     component.isCompletelyDirty = false;
 497:   }
 498: 
 499:   /**
 500:    * Return <code>true</code> if the specified component is completely
 501:    * contained within its dirty region, otherwise <code>false</code>
 502:    *
 503:    * @param component The component to check for complete dirtyness
 504:    *
 505:    * @return Whether the component is completely dirty
 506:    *
 507:    * @see #dirtyComponents
 508:    * @see #addDirtyRegion
 509:    * @see #getDirtyRegion
 510:    * @see #isCompletelyDirty
 511:    * @see #markCompletelyClean
 512:    */
 513:   public boolean isCompletelyDirty(JComponent component)
 514:   {
 515:     if (! dirtyComponents.containsKey(component))
 516:       return false;
 517:     return component.isCompletelyDirty;
 518:   }
 519: 
 520:   /**
 521:    * Validate all components which have been marked invalid in the {@link
 522:    * #invalidComponents} vector.
 523:    */
 524:   public void validateInvalidComponents()
 525:   {
 526:     // In order to keep the blocking of application threads minimal, we switch
 527:     // the invalidComponents field with the workInvalidComponents field and
 528:     // work with the workInvalidComponents field.
 529:     synchronized(this)
 530:     {
 531:       ArrayList swap = invalidComponents;
 532:       invalidComponents = workInvalidComponents;
 533:       workInvalidComponents = swap;
 534:     }
 535:     for (Iterator i = workInvalidComponents.iterator(); i.hasNext(); )
 536:       {
 537:         Component comp = (Component) i.next();
 538:         // Find validate root.
 539:         while ((!(comp instanceof JComponent)
 540:                || !((JComponent) comp).isValidateRoot())
 541:                && comp.getParent() != null)
 542:           comp = comp.getParent();
 543: 
 544:         // Validate the validate root.
 545:         if (! (comp.isVisible() && comp.isShowing()))
 546:           continue;
 547:         comp.validate();
 548:       }
 549:     workInvalidComponents.clear();
 550:   }
 551: 
 552:   /**
 553:    * Repaint all regions of all components which have been marked dirty in
 554:    * the {@link #dirtyComponents} table.
 555:    */
 556:   public synchronized void paintDirtyRegions()
 557:   {
 558:     // In order to keep the blocking of application threads minimal, we switch
 559:     // the dirtyComponents field with the workdirtyComponents field and the
 560:     // repaintOrder field with the workRepaintOrder field and work with the
 561:     // work* fields.
 562:     synchronized(this)
 563:     {
 564:       ArrayList swap = workRepaintOrder;
 565:       workRepaintOrder = repaintOrder;
 566:       repaintOrder = swap;
 567:       HashMap swap2 = workDirtyComponents;
 568:       workDirtyComponents = dirtyComponents;
 569:       dirtyComponents = swap2;
 570:     }
 571:     for (Iterator i = workRepaintOrder.iterator(); i.hasNext();)
 572:       {
 573:         JComponent comp = (JComponent) i.next();
 574:         // If a component is marked completely clean in the meantime, then skip
 575:         // it.
 576:         Rectangle damaged = (Rectangle) workDirtyComponents.get(comp);
 577:         if (damaged == null || damaged.isEmpty())
 578:           continue;
 579:         comp.paintImmediately(damaged);
 580:       }
 581:     workRepaintOrder.clear();
 582:     workDirtyComponents.clear();
 583:   }
 584: 
 585:   /**
 586:    * Get an offscreen buffer for painting a component's image. This image
 587:    * may be smaller than the proposed dimensions, depending on the value of
 588:    * the {@link #doubleBufferMaximumSize} property.
 589:    *
 590:    * @param component The component to return an offscreen buffer for
 591:    * @param proposedWidth The proposed width of the offscreen buffer
 592:    * @param proposedHeight The proposed height of the offscreen buffer
 593:    *
 594:    * @return A shared offscreen buffer for painting
 595:    *
 596:    * @see #doubleBuffer
 597:    */
 598:   public Image getOffscreenBuffer(Component component, int proposedWidth,
 599:                                   int proposedHeight)
 600:   {
 601:     if (doubleBuffer == null 
 602:         || (((doubleBuffer.getWidth(null) < proposedWidth) 
 603:              || (doubleBuffer.getHeight(null) < proposedHeight))
 604:             && (proposedWidth < doubleBufferMaximumSize.width)
 605:             && (proposedHeight < doubleBufferMaximumSize.height)))
 606:       {
 607:         doubleBuffer = component.createImage(proposedWidth, proposedHeight);
 608:       }
 609:     return doubleBuffer;
 610:   }
 611: 
 612:   /**
 613:    * Creates and returns a volatile offscreen buffer for the specified
 614:    * component that can be used as a double buffer. The returned image
 615:    * is a {@link VolatileImage}. Its size will be <code>(proposedWidth,
 616:    * proposedHeight)</code> except when the maximum double buffer size
 617:    * has been set in this RepaintManager.
 618:    *
 619:    * @param comp the Component for which to create a volatile buffer
 620:    * @param proposedWidth the proposed width of the buffer
 621:    * @param proposedHeight the proposed height of the buffer
 622:    *
 623:    * @since 1.4
 624:    *
 625:    * @see VolatileImage
 626:    */
 627:   public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
 628:                                           int proposedHeight)
 629:   {
 630:     int maxWidth = doubleBufferMaximumSize.width;
 631:     int maxHeight = doubleBufferMaximumSize.height;
 632:     return comp.createVolatileImage(Math.min(maxWidth, proposedWidth),
 633:                                     Math.min(maxHeight, proposedHeight));
 634:   }
 635:   
 636: 
 637:   /**
 638:    * Get the value of the {@link #doubleBufferMaximumSize} property.
 639:    *
 640:    * @return The current value of the property
 641:    *
 642:    * @see #setDoubleBufferMaximumSize
 643:    */
 644:   public Dimension getDoubleBufferMaximumSize()
 645:   {
 646:     return doubleBufferMaximumSize;
 647:   }
 648: 
 649:   /**
 650:    * Set the value of the {@link #doubleBufferMaximumSize} property.
 651:    *
 652:    * @param size The new value of the property
 653:    *
 654:    * @see #getDoubleBufferMaximumSize
 655:    */
 656:   public void setDoubleBufferMaximumSize(Dimension size)
 657:   {
 658:     doubleBufferMaximumSize = size;
 659:   }
 660: 
 661:   /**
 662:    * Set the value of the {@link #doubleBufferingEnabled} property.
 663:    *
 664:    * @param buffer The new value of the property
 665:    *
 666:    * @see #isDoubleBufferingEnabled
 667:    */
 668:   public void setDoubleBufferingEnabled(boolean buffer)
 669:   {
 670:     doubleBufferingEnabled = buffer;
 671:   }
 672: 
 673:   /**
 674:    * Get the value of the {@link #doubleBufferingEnabled} property.
 675:    *
 676:    * @return The current value of the property
 677:    *
 678:    * @see #setDoubleBufferingEnabled
 679:    */
 680:   public boolean isDoubleBufferingEnabled()
 681:   {
 682:     return doubleBufferingEnabled;
 683:   }
 684:   
 685:   public String toString()
 686:   {
 687:     return "RepaintManager";
 688:   }
 689: }