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 /**
63  * <p>The repaint manager holds a set of dirty regions, invalid components,
64  * and a double buffer surface.  The dirty regions and invalid components
65  * are used to coalesce multiple revalidate() and repaint() calls in the
66  * component tree into larger groups to be refreshed "all at once"; the
67  * double buffer surface is used by root components to paint
68  * themselves.</p>
69  *
70  * <p>See <a
71  * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
72  * document</a> for more details.</p>
73  * document</a> for more details.</p>
74  *
75  * @author Roman Kennke (kennke@aicas.com)
76  * @author Graydon Hoare (graydon@redhat.com)
77  * @author Audrius Meskauskas (audriusa@bioinformatics.org)
78  */
79 public class RepaintManager
80 {
81   /**
82    * An InvocationEvent subclass that implements LowPriorityEvent. This is used
83    * to defer the execution of RepaintManager requests as long as possible on
84    * the event queue. This way we make sure that all available input is
85    * processed before getting active with the RepaintManager. This allows
86    * for better optimization (more validate and repaint requests can be
87    * coalesced) and thus has a positive effect on performance for GUI
88    * applications under heavy load.
89    */
90   private static class RepaintWorkerEvent
91     extends InvocationEvent
92     implements LowPriorityEvent
93   {
94 
95     /**
96      * Creates a new RepaintManager event.
97      *
98      * @param source the source
99      * @param runnable the runnable to execute
100      */
RepaintWorkerEvent(Object source, Runnable runnable, Object notifier, boolean catchEx)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      */
dispatch()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 
RepaintWorker()148     public RepaintWorker()
149     {
150       live = false;
151     }
152 
setLive(boolean b)153     public synchronized void setLive(boolean b)
154     {
155       live = b;
156     }
157 
isLive()158     public synchronized boolean isLive()
159     {
160       return live;
161     }
162 
run()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    */
RepaintManager()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    */
currentManager(Component component)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    */
currentManager(JComponent component)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    */
setCurrentManager(RepaintManager manager)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    */
addInvalidComponent(JComponent component)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    */
removeInvalidComponent(JComponent component)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    */
addDirtyRegion(JComponent component, int x, int y, int w, int h)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    */
getDirtyRegion(JComponent component)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    */
markCompletelyDirty(JComponent component)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    */
markCompletelyClean(JComponent component)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    */
isCompletelyDirty(JComponent component)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    */
validateInvalidComponents()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    */
paintDirtyRegions()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    */
compileRepaintRoots(HashMap dirtyRegions, JComponent dirty, HashSet roots)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    */
getOffscreenBuffer(Component component, int proposedWidth, int proposedHeight)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    */
commitBuffer(Component comp, int x, int y, int w, int h)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    */
getVolatileOffscreenBuffer(Component comp, int proposedWidth, int proposedHeight)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    */
getDoubleBufferMaximumSize()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    */
setDoubleBufferMaximumSize(Dimension size)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    */
setDoubleBufferingEnabled(boolean buffer)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    */
isDoubleBufferingEnabled()837   public boolean isDoubleBufferingEnabled()
838   {
839     return doubleBufferingEnabled;
840   }
841 
toString()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    */
invokeLater(Runnable runnable)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 }
861