001/* RepaintManager.java --
002   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing;
040
041import gnu.classpath.SystemProperties;
042import gnu.java.awt.LowPriorityEvent;
043
044import java.applet.Applet;
045import java.awt.Component;
046import java.awt.Dimension;
047import java.awt.EventQueue;
048import java.awt.Graphics;
049import java.awt.Image;
050import java.awt.Rectangle;
051import java.awt.Toolkit;
052import java.awt.Window;
053import java.awt.event.InvocationEvent;
054import java.awt.image.VolatileImage;
055import java.util.ArrayList;
056import java.util.HashMap;
057import java.util.HashSet;
058import java.util.Iterator;
059import java.util.Set;
060import java.util.WeakHashMap;
061
062/**
063 * <p>The repaint manager holds a set of dirty regions, invalid components,
064 * and a double buffer surface.  The dirty regions and invalid components
065 * are used to coalesce multiple revalidate() and repaint() calls in the
066 * component tree into larger groups to be refreshed "all at once"; the
067 * double buffer surface is used by root components to paint
068 * themselves.</p>
069 *
070 * <p>See <a
071 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
072 * document</a> for more details.</p>
073 * document</a> for more details.</p>
074 *
075 * @author Roman Kennke (kennke@aicas.com)
076 * @author Graydon Hoare (graydon@redhat.com)
077 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
078 */
079public class RepaintManager
080{
081  /**
082   * An InvocationEvent subclass that implements LowPriorityEvent. This is used
083   * to defer the execution of RepaintManager requests as long as possible on
084   * the event queue. This way we make sure that all available input is
085   * processed before getting active with the RepaintManager. This allows
086   * for better optimization (more validate and repaint requests can be
087   * coalesced) and thus has a positive effect on performance for GUI
088   * applications under heavy load.
089   */
090  private static class RepaintWorkerEvent
091    extends InvocationEvent
092    implements LowPriorityEvent
093  {
094
095    /**
096     * Creates a new RepaintManager event.
097     *
098     * @param source the source
099     * @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}