OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / RepaintManager.java
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 }