1 /* RepaintManager.java --
2 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
41 import gnu.classpath.SystemProperties;
42 import gnu.java.awt.LowPriorityEvent;
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;
60 import java.util.WeakHashMap;
62 import javax.swing.text.JTextComponent;
65 * <p>The repaint manager holds a set of dirty regions, invalid components,
66 * and a double buffer surface. The dirty regions and invalid components
67 * are used to coalesce multiple revalidate() and repaint() calls in the
68 * component tree into larger groups to be refreshed "all at once"; the
69 * double buffer surface is used by root components to paint
73 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
74 * document</a> for more details.</p>
75 * document</a> for more details.</p>
77 * @author Roman Kennke (kennke@aicas.com)
78 * @author Graydon Hoare (graydon@redhat.com)
79 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
81 public class RepaintManager
84 * An InvocationEvent subclass that implements LowPriorityEvent. This is used
85 * to defer the execution of RepaintManager requests as long as possible on
86 * the event queue. This way we make sure that all available input is
87 * processed before getting active with the RepaintManager. This allows
88 * for better optimization (more validate and repaint requests can be
89 * coalesced) and thus has a positive effect on performance for GUI
90 * applications under heavy load.
92 private static class RepaintWorkerEvent
93 extends InvocationEvent
94 implements LowPriorityEvent
98 * Creates a new RepaintManager event.
100 * @param source the source
101 * @param runnable the runnable to execute
103 public RepaintWorkerEvent(Object source, Runnable runnable,
104 Object notifier, boolean catchEx)
106 super(source, runnable, notifier, catchEx);
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.
115 public void dispatch()
122 * The current repaint managers, indexed by their ThreadGroups.
124 static WeakHashMap currentRepaintManagers;
127 * A rectangle object to be reused in damaged regions calculation.
129 private static Rectangle rectCache = new Rectangle();
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>
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>
144 private class RepaintWorker
150 public RepaintWorker()
155 public synchronized void setLive(boolean b)
160 public synchronized boolean isLive()
169 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
171 (RepaintManager) currentRepaintManagers.get(threadGroup);
172 rm.validateInvalidComponents();
173 rm.paintDirtyRegions();
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.
189 * This is package private to avoid a synthetic accessor method in inner
192 * @see #addDirtyRegion
193 * @see #getDirtyRegion
194 * @see #isCompletelyDirty
195 * @see #markCompletelyClean
196 * @see #markCompletelyDirty
198 private HashMap dirtyComponents;
201 * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary
204 private HashMap dirtyComponentsWork;
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
212 * @see #addDirtyRegion
213 * @see #addInvalidComponent
215 private RepaintWorker repaintWorker;
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
223 * @see #addInvalidComponent
224 * @see #removeInvalidComponent
225 * @see #validateInvalidComponents
227 private ArrayList invalidComponents;
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.
234 * @see #isDoubleBufferingEnabled
235 * @see #setDoubleBufferingEnabled
237 private boolean doubleBufferingEnabled;
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
244 private WeakHashMap offscreenBuffers;
247 * The maximum width and height to allocate as a double buffer. Requests
248 * beyond this size are ignored.
250 * @see #paintDirtyRegions
251 * @see #getDoubleBufferMaximumSize
252 * @see #setDoubleBufferMaximumSize
254 private Dimension doubleBufferMaximumSize;
258 * Create a new RepaintManager object.
260 public RepaintManager()
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")
270 offscreenBuffers = new WeakHashMap();
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.
279 * @param component a component to look up the manager of
281 * @return the current repaint manager for the calling thread's thread group
282 * and the specified component
284 * @see #setCurrentManager
286 public static RepaintManager currentManager(Component component)
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)
295 currentManager = new RepaintManager();
296 currentRepaintManagers.put(threadGroup, currentManager);
298 return currentManager;
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.
307 * This method is only here for backwards compatibility with older versions
308 * of Swing and simply forwards to {@link #currentManager(Component)}.
310 * @param component a component to look up the manager of
312 * @return the current repaint manager for the calling thread's thread group
313 * and the specified component
315 * @see #setCurrentManager
317 public static RepaintManager currentManager(JComponent component)
319 return currentManager((Component)component);
323 * Sets the repaint manager for the calling thread's thread group.
325 * @param manager the repaint manager to set for the current thread's thread
328 * @see #currentManager(Component)
330 public static void setCurrentManager(RepaintManager manager)
332 if (currentRepaintManagers == null)
333 currentRepaintManagers = new WeakHashMap();
335 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
336 currentRepaintManagers.put(threadGroup, manager);
340 * Add a component to the {@link #invalidComponents} vector. If the
341 * {@link #repaintWorker} class is not active, insert it in the system
344 * @param component The component to add
346 * @see #removeInvalidComponent
348 public void addInvalidComponent(JComponent component)
350 Component validateRoot = null;
351 Component c = component;
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())
359 if (c instanceof JComponent && ((JComponent) c).isValidateRoot())
368 // If we didn't find a validate root, then we don't validate.
369 if (validateRoot == null)
372 // Make sure the validate root and all of it's ancestors are visible.
376 if (! c.isVisible() || ! c.isDisplayable())
381 if (invalidComponents.contains(validateRoot))
384 //synchronized (invalidComponents)
386 invalidComponents.add(validateRoot);
389 if (! repaintWorker.isLive())
391 repaintWorker.setLive(true);
392 invokeLater(repaintWorker);
397 * Remove a component from the {@link #invalidComponents} vector.
399 * @param component The component to remove
401 * @see #addInvalidComponent
403 public void removeInvalidComponent(JComponent component)
405 synchronized (invalidComponents)
407 invalidComponents.remove(component);
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.
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
423 * @see #addDirtyRegion
424 * @see #getDirtyRegion
425 * @see #isCompletelyDirty
426 * @see #markCompletelyClean
427 * @see #markCompletelyDirty
429 public void addDirtyRegion(JComponent component, int x, int y,
432 if (w <= 0 || h <= 0 || !component.isShowing())
434 component.computeVisibleRect(rectCache);
435 SwingUtilities.computeIntersection(x, y, w, h, rectCache);
437 if (! rectCache.isEmpty())
439 synchronized (dirtyComponents)
441 Rectangle dirtyRect = (Rectangle)dirtyComponents.get(component);
442 if (dirtyRect != null)
444 SwingUtilities.computeUnion(rectCache.x, rectCache.y,
445 rectCache.width, rectCache.height,
450 dirtyComponents.put(component, rectCache.getBounds());
454 if (! repaintWorker.isLive())
456 repaintWorker.setLive(true);
457 invokeLater(repaintWorker);
463 * Get the dirty region associated with a component, or <code>null</code>
464 * if the component has no dirty region.
466 * @param component The component to get the dirty region of
468 * @return The dirty region of the component
470 * @see #dirtyComponents
471 * @see #addDirtyRegion
472 * @see #isCompletelyDirty
473 * @see #markCompletelyClean
474 * @see #markCompletelyDirty
476 public Rectangle getDirtyRegion(JComponent component)
478 Rectangle dirty = (Rectangle) dirtyComponents.get(component);
480 dirty = new Rectangle();
485 * Mark a component as dirty over its entire bounds.
487 * @param component The component to mark as dirty
489 * @see #dirtyComponents
490 * @see #addDirtyRegion
491 * @see #getDirtyRegion
492 * @see #isCompletelyDirty
493 * @see #markCompletelyClean
495 public void markCompletelyDirty(JComponent component)
497 addDirtyRegion(component, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
501 * Remove all dirty regions for a specified component
503 * @param component The component to mark as clean
505 * @see #dirtyComponents
506 * @see #addDirtyRegion
507 * @see #getDirtyRegion
508 * @see #isCompletelyDirty
509 * @see #markCompletelyDirty
511 public void markCompletelyClean(JComponent component)
513 synchronized (dirtyComponents)
515 dirtyComponents.remove(component);
520 * Return <code>true</code> if the specified component is completely
521 * contained within its dirty region, otherwise <code>false</code>
523 * @param component The component to check for complete dirtyness
525 * @return Whether the component is completely dirty
527 * @see #dirtyComponents
528 * @see #addDirtyRegion
529 * @see #getDirtyRegion
530 * @see #isCompletelyDirty
531 * @see #markCompletelyClean
533 public boolean isCompletelyDirty(JComponent component)
535 boolean dirty = false;
536 Rectangle r = getDirtyRegion(component);
537 if(r.width == Integer.MAX_VALUE && r.height == Integer.MAX_VALUE)
543 * Validate all components which have been marked invalid in the {@link
544 * #invalidComponents} vector.
546 public void validateInvalidComponents()
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)
554 synchronized (invalidComponents)
556 comp = (Component) invalidComponents.remove(0);
558 // Validate the validate component.
559 if (! (comp.isVisible() && comp.isShowing()))
566 * Repaint all regions of all components which have been marked dirty in the
567 * {@link #dirtyComponents} table.
569 public void paintDirtyRegions()
571 // Short circuit if there is nothing to paint.
572 if (dirtyComponents.size() == 0)
575 // Swap dirtyRegions with dirtyRegionsWork to avoid locking.
576 synchronized (dirtyComponents)
578 HashMap swap = dirtyComponents;
579 dirtyComponents = dirtyComponentsWork;
580 dirtyComponentsWork = swap;
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();)
588 JComponent dirty = (JComponent) i.next();
589 compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots);
592 for (Iterator i = repaintRoots.iterator(); i.hasNext();)
594 JComponent comp = (JComponent) i.next();
595 Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp);
596 if (damaged == null || damaged.isEmpty())
598 comp.paintImmediately(damaged);
600 dirtyComponentsWork.clear();
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.
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
614 private void compileRepaintRoots(HashMap dirtyRegions, JComponent dirty,
617 Component current = dirty;
618 Component root = dirty;
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);
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();
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())
636 // The cumulated offsets.
639 // The actual offset for the found root.
643 // Search the highest component that is also marked dirty.
647 parent = current.getParent();
648 if (parent == null || !(parent instanceof JComponent))
652 // Update the offset.
660 w = current.getWidth();
661 h = current.getHeight();
662 rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache);
664 // Don't paint if the dirty regions is clipped away by any of
666 if (rectCache.isEmpty())
669 // We can skip to the next up when this parent is not dirty.
670 if (dirtyRegions.containsKey(parent))
678 // Merge the rectangles of the root and the requested component if
679 // the are different.
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);
689 // Adds the root to the roots set.
690 if (! roots.contains(root))
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.
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
703 * @return A shared offscreen buffer for painting
705 public Image getOffscreenBuffer(Component component, int proposedWidth,
708 Component root = SwingUtilities.getWindowAncestor(component);
709 Image buffer = (Image) offscreenBuffers.get(root);
711 || buffer.getWidth(null) < proposedWidth
712 || buffer.getHeight(null) < proposedHeight)
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);
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.
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
734 void commitBuffer(Component comp, int x, int y, int w, int h)
736 Component root = comp;
738 && ! (root instanceof Window || root instanceof Applet))
742 root = root.getParent();
747 Graphics g = root.getGraphics();
748 Image buffer = (Image) offscreenBuffers.get(root);
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);
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.
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
774 public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
777 Component root = SwingUtilities.getWindowAncestor(comp);
778 Image buffer = (Image) offscreenBuffers.get(root);
780 || buffer.getWidth(null) < proposedWidth
781 || buffer.getHeight(null) < proposedHeight
782 || !(buffer instanceof VolatileImage))
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);
790 offscreenBuffers.put(root, buffer);
797 * Get the value of the {@link #doubleBufferMaximumSize} property.
799 * @return The current value of the property
801 * @see #setDoubleBufferMaximumSize
803 public Dimension getDoubleBufferMaximumSize()
805 return doubleBufferMaximumSize;
809 * Set the value of the {@link #doubleBufferMaximumSize} property.
811 * @param size The new value of the property
813 * @see #getDoubleBufferMaximumSize
815 public void setDoubleBufferMaximumSize(Dimension size)
817 doubleBufferMaximumSize = size;
821 * Set the value of the {@link #doubleBufferingEnabled} property.
823 * @param buffer The new value of the property
825 * @see #isDoubleBufferingEnabled
827 public void setDoubleBufferingEnabled(boolean buffer)
829 doubleBufferingEnabled = buffer;
833 * Get the value of the {@link #doubleBufferingEnabled} property.
835 * @return The current value of the property
837 * @see #setDoubleBufferingEnabled
839 public boolean isDoubleBufferingEnabled()
841 return doubleBufferingEnabled;
844 public String toString()
846 return "RepaintManager";
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
855 private void invokeLater(Runnable runnable)
857 Toolkit tk = Toolkit.getDefaultToolkit();
858 EventQueue evQueue = tk.getSystemEventQueue();
859 InvocationEvent ev = new RepaintWorkerEvent(evQueue, runnable, null, false);
860 evQueue.postEvent(ev);