001    /* RepaintManager.java --
002       Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import gnu.classpath.SystemProperties;
042    import gnu.java.awt.LowPriorityEvent;
043    
044    import java.applet.Applet;
045    import java.awt.Component;
046    import java.awt.Dimension;
047    import java.awt.EventQueue;
048    import java.awt.Graphics;
049    import java.awt.Image;
050    import java.awt.Rectangle;
051    import java.awt.Toolkit;
052    import java.awt.Window;
053    import java.awt.event.InvocationEvent;
054    import java.awt.image.VolatileImage;
055    import java.util.ArrayList;
056    import java.util.HashMap;
057    import java.util.HashSet;
058    import java.util.Iterator;
059    import java.util.Set;
060    import 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     */
079    public 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    }