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 import javax.swing.text.JTextComponent;
063
064 /**
065 * <p>The repaint manager holds a set of dirty regions, invalid components,
066 * and a double buffer surface. The dirty regions and invalid components
067 * are used to coalesce multiple revalidate() and repaint() calls in the
068 * component tree into larger groups to be refreshed "all at once"; the
069 * double buffer surface is used by root components to paint
070 * themselves.</p>
071 *
072 * <p>See <a
073 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
074 * document</a> for more details.</p>
075 * document</a> for more details.</p>
076 *
077 * @author Roman Kennke (kennke@aicas.com)
078 * @author Graydon Hoare (graydon@redhat.com)
079 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
080 */
081 public class RepaintManager
082 {
083 /**
084 * An InvocationEvent subclass that implements LowPriorityEvent. This is used
085 * to defer the execution of RepaintManager requests as long as possible on
086 * the event queue. This way we make sure that all available input is
087 * processed before getting active with the RepaintManager. This allows
088 * for better optimization (more validate and repaint requests can be
089 * coalesced) and thus has a positive effect on performance for GUI
090 * applications under heavy load.
091 */
092 private static class RepaintWorkerEvent
093 extends InvocationEvent
094 implements LowPriorityEvent
095 {
096
097 /**
098 * Creates a new RepaintManager event.
099 *
100 * @param source the source
101 * @param runnable the runnable to execute
102 */
103 public RepaintWorkerEvent(Object source, Runnable runnable,
104 Object notifier, boolean catchEx)
105 {
106 super(source, runnable, notifier, catchEx);
107 }
108
109 /**
110 * An application that I met implements its own event dispatching and
111 * calls dispatch() via reflection, and only checks declared methods,
112 * that is, it expects this method to be in the event's class, not
113 * in a superclass. So I put this in here... sigh.
114 */
115 public void dispatch()
116 {
117 super.dispatch();
118 }
119 }
120
121 /**
122 * The current repaint managers, indexed by their ThreadGroups.
123 */
124 static WeakHashMap currentRepaintManagers;
125
126 /**
127 * A rectangle object to be reused in damaged regions calculation.
128 */
129 private static Rectangle rectCache = new Rectangle();
130
131 /**
132 * <p>A helper class which is placed into the system event queue at
133 * various times in order to facilitate repainting and layout. There is
134 * typically only one of these objects active at any time. When the
135 * {@link RepaintManager} is told to queue a repaint, it checks to see if
136 * a {@link RepaintWorker} is "live" in the system event queue, and if
137 * not it inserts one using {@link SwingUtilities#invokeLater}.</p>
138 *
139 * <p>When the {@link RepaintWorker} comes to the head of the system
140 * event queue, its {@link RepaintWorker#run} method is executed by the
141 * swing paint thread, which revalidates all invalid components and
142 * repaints any damage in the swing scene.</p>
143 */
144 private class RepaintWorker
145 implements Runnable
146 {
147
148 boolean live;
149
150 public RepaintWorker()
151 {
152 live = false;
153 }
154
155 public synchronized void setLive(boolean b)
156 {
157 live = b;
158 }
159
160 public synchronized boolean isLive()
161 {
162 return live;
163 }
164
165 public void run()
166 {
167 try
168 {
169 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
170 RepaintManager rm =
171 (RepaintManager) currentRepaintManagers.get(threadGroup);
172 rm.validateInvalidComponents();
173 rm.paintDirtyRegions();
174 }
175 finally
176 {
177 setLive(false);
178 }
179 }
180
181 }
182
183 /**
184 * A table storing the dirty regions of components. The keys of this
185 * table are components, the values are rectangles. Each component maps
186 * to exactly one rectangle. When more regions are marked as dirty on a
187 * component, they are union'ed with the existing rectangle.
188 *
189 * This is package private to avoid a synthetic accessor method in inner
190 * class.
191 *
192 * @see #addDirtyRegion
193 * @see #getDirtyRegion
194 * @see #isCompletelyDirty
195 * @see #markCompletelyClean
196 * @see #markCompletelyDirty
197 */
198 private HashMap dirtyComponents;
199
200 /**
201 * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary
202 * locking.
203 */
204 private HashMap dirtyComponentsWork;
205
206 /**
207 * A single, shared instance of the helper class. Any methods which mark
208 * components as invalid or dirty eventually activate this instance. It
209 * is added to the event queue if it is not already active, otherwise
210 * reused.
211 *
212 * @see #addDirtyRegion
213 * @see #addInvalidComponent
214 */
215 private RepaintWorker repaintWorker;
216
217 /**
218 * The set of components which need revalidation, in the "layout" sense.
219 * There is no additional information about "what kind of layout" they
220 * need (as there is with dirty regions), so it is just a vector rather
221 * than a table.
222 *
223 * @see #addInvalidComponent
224 * @see #removeInvalidComponent
225 * @see #validateInvalidComponents
226 */
227 private ArrayList invalidComponents;
228
229 /**
230 * Whether or not double buffering is enabled on this repaint
231 * manager. This is merely a hint to clients; the RepaintManager will
232 * always return an offscreen buffer when one is requested.
233 *
234 * @see #isDoubleBufferingEnabled
235 * @see #setDoubleBufferingEnabled
236 */
237 private boolean doubleBufferingEnabled;
238
239 /**
240 * The offscreen buffers. This map holds one offscreen buffer per
241 * Window/Applet and releases them as soon as the Window/Applet gets garbage
242 * collected.
243 */
244 private WeakHashMap offscreenBuffers;
245
246 /**
247 * The maximum width and height to allocate as a double buffer. Requests
248 * beyond this size are ignored.
249 *
250 * @see #paintDirtyRegions
251 * @see #getDoubleBufferMaximumSize
252 * @see #setDoubleBufferMaximumSize
253 */
254 private Dimension doubleBufferMaximumSize;
255
256
257 /**
258 * Create a new RepaintManager object.
259 */
260 public RepaintManager()
261 {
262 dirtyComponents = new HashMap();
263 dirtyComponentsWork = new HashMap();
264 invalidComponents = new ArrayList();
265 repaintWorker = new RepaintWorker();
266 doubleBufferMaximumSize = new Dimension(2000,2000);
267 doubleBufferingEnabled =
268 SystemProperties.getProperty("gnu.swing.doublebuffering", "true")
269 .equals("true");
270 offscreenBuffers = new WeakHashMap();
271 }
272
273 /**
274 * Returns the <code>RepaintManager</code> for the current thread's
275 * thread group. The default implementation ignores the
276 * <code>component</code> parameter and returns the same repaint manager
277 * for all components.
278 *
279 * @param component a component to look up the manager of
280 *
281 * @return the current repaint manager for the calling thread's thread group
282 * and the specified component
283 *
284 * @see #setCurrentManager
285 */
286 public static RepaintManager currentManager(Component component)
287 {
288 if (currentRepaintManagers == null)
289 currentRepaintManagers = new WeakHashMap();
290 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
291 RepaintManager currentManager =
292 (RepaintManager) currentRepaintManagers.get(threadGroup);
293 if (currentManager == null)
294 {
295 currentManager = new RepaintManager();
296 currentRepaintManagers.put(threadGroup, currentManager);
297 }
298 return currentManager;
299 }
300
301 /**
302 * Returns the <code>RepaintManager</code> for the current thread's
303 * thread group. The default implementation ignores the
304 * <code>component</code> parameter and returns the same repaint manager
305 * for all components.
306 *
307 * This method is only here for backwards compatibility with older versions
308 * of Swing and simply forwards to {@link #currentManager(Component)}.
309 *
310 * @param component a component to look up the manager of
311 *
312 * @return the current repaint manager for the calling thread's thread group
313 * and the specified component
314 *
315 * @see #setCurrentManager
316 */
317 public static RepaintManager currentManager(JComponent component)
318 {
319 return currentManager((Component)component);
320 }
321
322 /**
323 * Sets the repaint manager for the calling thread's thread group.
324 *
325 * @param manager the repaint manager to set for the current thread's thread
326 * group
327 *
328 * @see #currentManager(Component)
329 */
330 public static void setCurrentManager(RepaintManager manager)
331 {
332 if (currentRepaintManagers == null)
333 currentRepaintManagers = new WeakHashMap();
334
335 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
336 currentRepaintManagers.put(threadGroup, manager);
337 }
338
339 /**
340 * Add a component to the {@link #invalidComponents} vector. If the
341 * {@link #repaintWorker} class is not active, insert it in the system
342 * event queue.
343 *
344 * @param component The component to add
345 *
346 * @see #removeInvalidComponent
347 */
348 public void addInvalidComponent(JComponent component)
349 {
350 Component validateRoot = null;
351 Component c = component;
352 while (c != null)
353 {
354 // Special cases we don't bother validating are when the invalidated
355 // component (or any of it's ancestors) is inside a CellRendererPane
356 // or if it doesn't have a peer yet (== not displayable).
357 if (c instanceof CellRendererPane || ! c.isDisplayable())
358 return;
359 if (c instanceof JComponent && ((JComponent) c).isValidateRoot())
360 {
361 validateRoot = c;
362 break;
363 }
364
365 c = c.getParent();
366 }
367
368 // If we didn't find a validate root, then we don't validate.
369 if (validateRoot == null)
370 return;
371
372 // Make sure the validate root and all of it's ancestors are visible.
373 c = validateRoot;
374 while (c != null)
375 {
376 if (! c.isVisible() || ! c.isDisplayable())
377 return;
378 c = c.getParent();
379 }
380
381 if (invalidComponents.contains(validateRoot))
382 return;
383
384 //synchronized (invalidComponents)
385 // {
386 invalidComponents.add(validateRoot);
387 // }
388
389 if (! repaintWorker.isLive())
390 {
391 repaintWorker.setLive(true);
392 invokeLater(repaintWorker);
393 }
394 }
395
396 /**
397 * Remove a component from the {@link #invalidComponents} vector.
398 *
399 * @param component The component to remove
400 *
401 * @see #addInvalidComponent
402 */
403 public void removeInvalidComponent(JComponent component)
404 {
405 synchronized (invalidComponents)
406 {
407 invalidComponents.remove(component);
408 }
409 }
410
411 /**
412 * Add a region to the set of dirty regions for a specified component.
413 * This involves union'ing the new region with any existing dirty region
414 * associated with the component. If the {@link #repaintWorker} class
415 * is not active, insert it in the system event queue.
416 *
417 * @param component The component to add a dirty region for
418 * @param x The left x coordinate of the new dirty region
419 * @param y The top y coordinate of the new dirty region
420 * @param w The width of the new dirty region
421 * @param h The height of the new dirty region
422 *
423 * @see #addDirtyRegion
424 * @see #getDirtyRegion
425 * @see #isCompletelyDirty
426 * @see #markCompletelyClean
427 * @see #markCompletelyDirty
428 */
429 public void addDirtyRegion(JComponent component, int x, int y,
430 int w, int h)
431 {
432 if (w <= 0 || h <= 0 || !component.isShowing())
433 return;
434 component.computeVisibleRect(rectCache);
435 SwingUtilities.computeIntersection(x, y, w, h, rectCache);
436
437 if (! rectCache.isEmpty())
438 {
439 synchronized (dirtyComponents)
440 {
441 Rectangle dirtyRect = (Rectangle)dirtyComponents.get(component);
442 if (dirtyRect != null)
443 {
444 SwingUtilities.computeUnion(rectCache.x, rectCache.y,
445 rectCache.width, rectCache.height,
446 dirtyRect);
447 }
448 else
449 {
450 dirtyComponents.put(component, rectCache.getBounds());
451 }
452 }
453
454 if (! repaintWorker.isLive())
455 {
456 repaintWorker.setLive(true);
457 invokeLater(repaintWorker);
458 }
459 }
460 }
461
462 /**
463 * Get the dirty region associated with a component, or <code>null</code>
464 * if the component has no dirty region.
465 *
466 * @param component The component to get the dirty region of
467 *
468 * @return The dirty region of the component
469 *
470 * @see #dirtyComponents
471 * @see #addDirtyRegion
472 * @see #isCompletelyDirty
473 * @see #markCompletelyClean
474 * @see #markCompletelyDirty
475 */
476 public Rectangle getDirtyRegion(JComponent component)
477 {
478 Rectangle dirty = (Rectangle) dirtyComponents.get(component);
479 if (dirty == null)
480 dirty = new Rectangle();
481 return dirty;
482 }
483
484 /**
485 * Mark a component as dirty over its entire bounds.
486 *
487 * @param component The component to mark as dirty
488 *
489 * @see #dirtyComponents
490 * @see #addDirtyRegion
491 * @see #getDirtyRegion
492 * @see #isCompletelyDirty
493 * @see #markCompletelyClean
494 */
495 public void markCompletelyDirty(JComponent component)
496 {
497 addDirtyRegion(component, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
498 }
499
500 /**
501 * Remove all dirty regions for a specified component
502 *
503 * @param component The component to mark as clean
504 *
505 * @see #dirtyComponents
506 * @see #addDirtyRegion
507 * @see #getDirtyRegion
508 * @see #isCompletelyDirty
509 * @see #markCompletelyDirty
510 */
511 public void markCompletelyClean(JComponent component)
512 {
513 synchronized (dirtyComponents)
514 {
515 dirtyComponents.remove(component);
516 }
517 }
518
519 /**
520 * Return <code>true</code> if the specified component is completely
521 * contained within its dirty region, otherwise <code>false</code>
522 *
523 * @param component The component to check for complete dirtyness
524 *
525 * @return Whether the component is completely dirty
526 *
527 * @see #dirtyComponents
528 * @see #addDirtyRegion
529 * @see #getDirtyRegion
530 * @see #isCompletelyDirty
531 * @see #markCompletelyClean
532 */
533 public boolean isCompletelyDirty(JComponent component)
534 {
535 boolean dirty = false;
536 Rectangle r = getDirtyRegion(component);
537 if(r.width == Integer.MAX_VALUE && r.height == Integer.MAX_VALUE)
538 dirty = true;
539 return dirty;
540 }
541
542 /**
543 * Validate all components which have been marked invalid in the {@link
544 * #invalidComponents} vector.
545 */
546 public void validateInvalidComponents()
547 {
548 // We don't use an iterator here because that would fail when there are
549 // components invalidated during the validation of others, which happens
550 // quite frequently. Instead we synchronize the access a little more.
551 while (invalidComponents.size() > 0)
552 {
553 Component comp;
554 synchronized (invalidComponents)
555 {
556 comp = (Component) invalidComponents.remove(0);
557 }
558 // Validate the validate component.
559 if (! (comp.isVisible() && comp.isShowing()))
560 continue;
561 comp.validate();
562 }
563 }
564
565 /**
566 * Repaint all regions of all components which have been marked dirty in the
567 * {@link #dirtyComponents} table.
568 */
569 public void paintDirtyRegions()
570 {
571 // Short circuit if there is nothing to paint.
572 if (dirtyComponents.size() == 0)
573 return;
574
575 // Swap dirtyRegions with dirtyRegionsWork to avoid locking.
576 synchronized (dirtyComponents)
577 {
578 HashMap swap = dirtyComponents;
579 dirtyComponents = dirtyComponentsWork;
580 dirtyComponentsWork = swap;
581 }
582
583 // Compile a set of repaint roots.
584 HashSet repaintRoots = new HashSet();
585 Set components = dirtyComponentsWork.keySet();
586 for (Iterator i = components.iterator(); i.hasNext();)
587 {
588 JComponent dirty = (JComponent) i.next();
589 compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots);
590 }
591
592 for (Iterator i = repaintRoots.iterator(); i.hasNext();)
593 {
594 JComponent comp = (JComponent) i.next();
595 Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp);
596 if (damaged == null || damaged.isEmpty())
597 continue;
598 comp.paintImmediately(damaged);
599 }
600 dirtyComponentsWork.clear();
601 }
602
603 /**
604 * Compiles a list of components that really get repainted. This is called
605 * once for each component in the dirtyRegions HashMap, each time with
606 * another <code>dirty</code> parameter. This searches up the component
607 * hierarchy of <code>dirty</code> to find the highest parent that is also
608 * marked dirty and merges the dirty regions.
609 *
610 * @param dirtyRegions the dirty regions
611 * @param dirty the component for which to find the repaint root
612 * @param roots the list to which new repaint roots get appended
613 */
614 private void compileRepaintRoots(HashMap dirtyRegions, JComponent dirty,
615 HashSet roots)
616 {
617 Component current = dirty;
618 Component root = dirty;
619
620 // This will contain the dirty region in the root coordinate system,
621 // possibly clipped by ancestor's bounds.
622 Rectangle originalDirtyRect = (Rectangle) dirtyRegions.get(dirty);
623 rectCache.setBounds(originalDirtyRect);
624
625 // The bounds of the current component.
626 int x = dirty.getX();
627 int y = dirty.getY();
628 int w = dirty.getWidth();
629 int h = dirty.getHeight();
630
631 // Do nothing if dirty region is clipped away by the component's bounds.
632 rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache);
633 if (rectCache.isEmpty())
634 return;
635
636 // The cumulated offsets.
637 int dx = 0;
638 int dy = 0;
639 // The actual offset for the found root.
640 int rootDx = 0;
641 int rootDy = 0;
642
643 // Search the highest component that is also marked dirty.
644 Component parent;
645 while (true)
646 {
647 parent = current.getParent();
648 if (parent == null || !(parent instanceof JComponent))
649 break;
650
651 current = parent;
652 // Update the offset.
653 dx += x;
654 dy += y;
655 rectCache.x += x;
656 rectCache.y += y;
657
658 x = current.getX();
659 y = current.getY();
660 w = current.getWidth();
661 h = current.getHeight();
662 rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache);
663
664 // Don't paint if the dirty regions is clipped away by any of
665 // its ancestors.
666 if (rectCache.isEmpty())
667 return;
668
669 // We can skip to the next up when this parent is not dirty.
670 if (dirtyRegions.containsKey(parent))
671 {
672 root = current;
673 rootDx = dx;
674 rootDy = dy;
675 }
676 }
677
678 // Merge the rectangles of the root and the requested component if
679 // the are different.
680 if (root != dirty)
681 {
682 rectCache.x += rootDx - dx;
683 rectCache.y += rootDy - dy;
684 Rectangle dirtyRect = (Rectangle) dirtyRegions.get(root);
685 SwingUtilities.computeUnion(rectCache.x, rectCache.y, rectCache.width,
686 rectCache.height, dirtyRect);
687 }
688
689 // Adds the root to the roots set.
690 if (! roots.contains(root))
691 roots.add(root);
692 }
693
694 /**
695 * Get an offscreen buffer for painting a component's image. This image
696 * may be smaller than the proposed dimensions, depending on the value of
697 * the {@link #doubleBufferMaximumSize} property.
698 *
699 * @param component The component to return an offscreen buffer for
700 * @param proposedWidth The proposed width of the offscreen buffer
701 * @param proposedHeight The proposed height of the offscreen buffer
702 *
703 * @return A shared offscreen buffer for painting
704 */
705 public Image getOffscreenBuffer(Component component, int proposedWidth,
706 int proposedHeight)
707 {
708 Component root = SwingUtilities.getWindowAncestor(component);
709 Image buffer = (Image) offscreenBuffers.get(root);
710 if (buffer == null
711 || buffer.getWidth(null) < proposedWidth
712 || buffer.getHeight(null) < proposedHeight)
713 {
714 int width = Math.max(proposedWidth, root.getWidth());
715 width = Math.min(doubleBufferMaximumSize.width, width);
716 int height = Math.max(proposedHeight, root.getHeight());
717 height = Math.min(doubleBufferMaximumSize.height, height);
718 buffer = component.createImage(width, height);
719 offscreenBuffers.put(root, buffer);
720 }
721 return buffer;
722 }
723
724 /**
725 * Blits the back buffer of the specified root component to the screen.
726 * This is package private because it must get called by JComponent.
727 *
728 * @param comp the component to be painted
729 * @param x the area to paint on screen, in comp coordinates
730 * @param y the area to paint on screen, in comp coordinates
731 * @param w the area to paint on screen, in comp coordinates
732 * @param h the area to paint on screen, in comp coordinates
733 */
734 void commitBuffer(Component comp, int x, int y, int w, int h)
735 {
736 Component root = comp;
737 while (root != null
738 && ! (root instanceof Window || root instanceof Applet))
739 {
740 x += root.getX();
741 y += root.getY();
742 root = root.getParent();
743 }
744
745 if (root != null)
746 {
747 Graphics g = root.getGraphics();
748 Image buffer = (Image) offscreenBuffers.get(root);
749 if (buffer != null)
750 {
751 // Make sure we have a sane clip at this point.
752 g.clipRect(x, y, w, h);
753 g.drawImage(buffer, 0, 0, root);
754 g.dispose();
755 }
756 }
757 }
758
759 /**
760 * Creates and returns a volatile offscreen buffer for the specified
761 * component that can be used as a double buffer. The returned image
762 * is a {@link VolatileImage}. Its size will be <code>(proposedWidth,
763 * proposedHeight)</code> except when the maximum double buffer size
764 * has been set in this RepaintManager.
765 *
766 * @param comp the Component for which to create a volatile buffer
767 * @param proposedWidth the proposed width of the buffer
768 * @param proposedHeight the proposed height of the buffer
769 *
770 * @since 1.4
771 *
772 * @see VolatileImage
773 */
774 public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
775 int proposedHeight)
776 {
777 Component root = SwingUtilities.getWindowAncestor(comp);
778 Image buffer = (Image) offscreenBuffers.get(root);
779 if (buffer == null
780 || buffer.getWidth(null) < proposedWidth
781 || buffer.getHeight(null) < proposedHeight
782 || !(buffer instanceof VolatileImage))
783 {
784 int width = Math.max(proposedWidth, root.getWidth());
785 width = Math.min(doubleBufferMaximumSize.width, width);
786 int height = Math.max(proposedHeight, root.getHeight());
787 height = Math.min(doubleBufferMaximumSize.height, height);
788 buffer = root.createVolatileImage(width, height);
789 if (buffer != null)
790 offscreenBuffers.put(root, buffer);
791 }
792 return buffer;
793 }
794
795
796 /**
797 * Get the value of the {@link #doubleBufferMaximumSize} property.
798 *
799 * @return The current value of the property
800 *
801 * @see #setDoubleBufferMaximumSize
802 */
803 public Dimension getDoubleBufferMaximumSize()
804 {
805 return doubleBufferMaximumSize;
806 }
807
808 /**
809 * Set the value of the {@link #doubleBufferMaximumSize} property.
810 *
811 * @param size The new value of the property
812 *
813 * @see #getDoubleBufferMaximumSize
814 */
815 public void setDoubleBufferMaximumSize(Dimension size)
816 {
817 doubleBufferMaximumSize = size;
818 }
819
820 /**
821 * Set the value of the {@link #doubleBufferingEnabled} property.
822 *
823 * @param buffer The new value of the property
824 *
825 * @see #isDoubleBufferingEnabled
826 */
827 public void setDoubleBufferingEnabled(boolean buffer)
828 {
829 doubleBufferingEnabled = buffer;
830 }
831
832 /**
833 * Get the value of the {@link #doubleBufferingEnabled} property.
834 *
835 * @return The current value of the property
836 *
837 * @see #setDoubleBufferingEnabled
838 */
839 public boolean isDoubleBufferingEnabled()
840 {
841 return doubleBufferingEnabled;
842 }
843
844 public String toString()
845 {
846 return "RepaintManager";
847 }
848
849 /**
850 * Sends an RepaintManagerEvent to the event queue with the specified
851 * runnable. This is similar to SwingUtilities.invokeLater(), only that the
852 * event is a low priority event in order to defer the execution a little
853 * more.
854 */
855 private void invokeLater(Runnable runnable)
856 {
857 Toolkit tk = Toolkit.getDefaultToolkit();
858 EventQueue evQueue = tk.getSystemEventQueue();
859 InvocationEvent ev = new RepaintWorkerEvent(evQueue, runnable, null, false);
860 evQueue.postEvent(ev);
861 }
862 }