OSDN Git Service

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