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 java.applet.Applet;
42 import java.awt.Component;
43 import java.awt.Dimension;
44 import java.awt.Graphics;
45 import java.awt.Image;
46 import java.awt.Rectangle;
47 import java.awt.Window;
48 import java.awt.image.VolatileImage;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.Iterator;
55 import java.util.WeakHashMap;
58 * <p>The repaint manager holds a set of dirty regions, invalid components,
59 * and a double buffer surface. The dirty regions and invalid components
60 * are used to coalesce multiple revalidate() and repaint() calls in the
61 * component tree into larger groups to be refreshed "all at once"; the
62 * double buffer surface is used by root components to paint
66 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
67 * document</a> for more details.</p>
69 * @author Roman Kennke (kennke@aicas.com)
70 * @author Graydon Hoare (graydon@redhat.com)
71 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
73 public class RepaintManager
76 * The current repaint managers, indexed by their ThreadGroups.
78 static WeakHashMap currentRepaintManagers;
81 * A rectangle object to be reused in damaged regions calculation.
83 private static Rectangle rectCache = new Rectangle();
86 * <p>A helper class which is placed into the system event queue at
87 * various times in order to facilitate repainting and layout. There is
88 * typically only one of these objects active at any time. When the
89 * {@link RepaintManager} is told to queue a repaint, it checks to see if
90 * a {@link RepaintWorker} is "live" in the system event queue, and if
91 * not it inserts one using {@link SwingUtilities#invokeLater}.</p>
93 * <p>When the {@link RepaintWorker} comes to the head of the system
94 * event queue, its {@link RepaintWorker#run} method is executed by the
95 * swing paint thread, which revalidates all invalid components and
96 * repaints any damage in the swing scene.</p>
98 private class RepaintWorker
104 public RepaintWorker()
109 public synchronized void setLive(boolean b)
114 public synchronized boolean isLive()
123 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
125 (RepaintManager) currentRepaintManagers.get(threadGroup);
126 rm.validateInvalidComponents();
127 rm.paintDirtyRegions();
138 * A table storing the dirty regions of components. The keys of this
139 * table are components, the values are rectangles. Each component maps
140 * to exactly one rectangle. When more regions are marked as dirty on a
141 * component, they are union'ed with the existing rectangle.
143 * This is package private to avoid a synthetic accessor method in inner
146 * @see #addDirtyRegion
147 * @see #getDirtyRegion
148 * @see #isCompletelyDirty
149 * @see #markCompletelyClean
150 * @see #markCompletelyDirty
152 private HashMap dirtyComponents;
155 * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary
158 private HashMap dirtyComponentsWork;
161 * A single, shared instance of the helper class. Any methods which mark
162 * components as invalid or dirty eventually activate this instance. It
163 * is added to the event queue if it is not already active, otherwise
166 * @see #addDirtyRegion
167 * @see #addInvalidComponent
169 private RepaintWorker repaintWorker;
172 * The set of components which need revalidation, in the "layout" sense.
173 * There is no additional information about "what kind of layout" they
174 * need (as there is with dirty regions), so it is just a vector rather
177 * @see #addInvalidComponent
178 * @see #removeInvalidComponent
179 * @see #validateInvalidComponents
181 private ArrayList invalidComponents;
184 * Whether or not double buffering is enabled on this repaint
185 * manager. This is merely a hint to clients; the RepaintManager will
186 * always return an offscreen buffer when one is requested.
188 * @see #isDoubleBufferingEnabled
189 * @see #setDoubleBufferingEnabled
191 private boolean doubleBufferingEnabled;
194 * The offscreen buffers. This map holds one offscreen buffer per
195 * Window/Applet and releases them as soon as the Window/Applet gets garbage
198 private WeakHashMap offscreenBuffers;
201 * Indicates if the RepaintManager is currently repainting an area.
203 private boolean repaintUnderway;
206 * This holds buffer commit requests when the RepaintManager is working.
207 * This maps Component objects (the top level components) to Rectangle
208 * objects (the area of the corresponding buffer that must be blitted on
211 private HashMap commitRequests;
214 * The maximum width and height to allocate as a double buffer. Requests
215 * beyond this size are ignored.
217 * @see #paintDirtyRegions
218 * @see #getDoubleBufferMaximumSize
219 * @see #setDoubleBufferMaximumSize
221 private Dimension doubleBufferMaximumSize;
225 * Create a new RepaintManager object.
227 public RepaintManager()
229 dirtyComponents = new HashMap();
230 dirtyComponentsWork = new HashMap();
231 invalidComponents = new ArrayList();
232 repaintWorker = new RepaintWorker();
233 doubleBufferMaximumSize = new Dimension(2000,2000);
234 doubleBufferingEnabled = true;
235 offscreenBuffers = new WeakHashMap();
236 repaintUnderway = false;
237 commitRequests = new HashMap();
241 * Returns the <code>RepaintManager</code> for the current thread's
242 * thread group. The default implementation ignores the
243 * <code>component</code> parameter and returns the same repaint manager
244 * for all components.
246 * @param component a component to look up the manager of
248 * @return the current repaint manager for the calling thread's thread group
249 * and the specified component
251 * @see #setCurrentManager
253 public static RepaintManager currentManager(Component component)
255 if (currentRepaintManagers == null)
256 currentRepaintManagers = new WeakHashMap();
257 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
258 RepaintManager currentManager =
259 (RepaintManager) currentRepaintManagers.get(threadGroup);
260 if (currentManager == null)
262 currentManager = new RepaintManager();
263 currentRepaintManagers.put(threadGroup, currentManager);
265 return currentManager;
269 * Returns the <code>RepaintManager</code> for the current thread's
270 * thread group. The default implementation ignores the
271 * <code>component</code> parameter and returns the same repaint manager
272 * for all components.
274 * This method is only here for backwards compatibility with older versions
275 * of Swing and simply forwards to {@link #currentManager(Component)}.
277 * @param component a component to look up the manager of
279 * @return the current repaint manager for the calling thread's thread group
280 * and the specified component
282 * @see #setCurrentManager
284 public static RepaintManager currentManager(JComponent component)
286 return currentManager((Component)component);
290 * Sets the repaint manager for the calling thread's thread group.
292 * @param manager the repaint manager to set for the current thread's thread
295 * @see #currentManager(Component)
297 public static void setCurrentManager(RepaintManager manager)
299 if (currentRepaintManagers == null)
300 currentRepaintManagers = new WeakHashMap();
302 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
303 currentRepaintManagers.put(threadGroup, manager);
307 * Add a component to the {@link #invalidComponents} vector. If the
308 * {@link #repaintWorker} class is not active, insert it in the system
311 * @param component The component to add
313 * @see #removeInvalidComponent
315 public void addInvalidComponent(JComponent component)
317 Component ancestor = component;
319 while (ancestor != null
320 && (! (ancestor instanceof JComponent)
321 || ! ((JComponent) ancestor).isValidateRoot() ))
322 ancestor = ancestor.getParent();
325 && ancestor instanceof JComponent
326 && ((JComponent) ancestor).isValidateRoot())
327 component = (JComponent) ancestor;
329 if (invalidComponents.contains(component))
332 synchronized (invalidComponents)
334 invalidComponents.add(component);
337 if (! repaintWorker.isLive())
339 repaintWorker.setLive(true);
340 SwingUtilities.invokeLater(repaintWorker);
345 * Remove a component from the {@link #invalidComponents} vector.
347 * @param component The component to remove
349 * @see #addInvalidComponent
351 public void removeInvalidComponent(JComponent component)
353 synchronized (invalidComponents)
355 invalidComponents.remove(component);
360 * Add a region to the set of dirty regions for a specified component.
361 * This involves union'ing the new region with any existing dirty region
362 * associated with the component. If the {@link #repaintWorker} class
363 * is not active, insert it in the system event queue.
365 * @param component The component to add a dirty region for
366 * @param x The left x coordinate of the new dirty region
367 * @param y The top y coordinate of the new dirty region
368 * @param w The width of the new dirty region
369 * @param h The height of the new dirty region
371 * @see #addDirtyRegion
372 * @see #getDirtyRegion
373 * @see #isCompletelyDirty
374 * @see #markCompletelyClean
375 * @see #markCompletelyDirty
377 public void addDirtyRegion(JComponent component, int x, int y,
380 if (w <= 0 || h <= 0 || !component.isShowing())
383 Component parent = component.getParent();
385 component.computeVisibleRect(rectCache);
386 SwingUtilities.computeIntersection(x, y, w, h, rectCache);
388 if (! rectCache.isEmpty())
390 if (dirtyComponents.containsKey(component))
392 SwingUtilities.computeUnion(rectCache.x, rectCache.y,
393 rectCache.width, rectCache.height,
394 (Rectangle) dirtyComponents.get(component));
398 synchronized (dirtyComponents)
400 dirtyComponents.put(component, rectCache.getBounds());
404 if (! repaintWorker.isLive())
406 repaintWorker.setLive(true);
407 SwingUtilities.invokeLater(repaintWorker);
413 * Get the dirty region associated with a component, or <code>null</code>
414 * if the component has no dirty region.
416 * @param component The component to get the dirty region of
418 * @return The dirty region of the component
420 * @see #dirtyComponents
421 * @see #addDirtyRegion
422 * @see #isCompletelyDirty
423 * @see #markCompletelyClean
424 * @see #markCompletelyDirty
426 public Rectangle getDirtyRegion(JComponent component)
428 Rectangle dirty = (Rectangle) dirtyComponents.get(component);
430 dirty = new Rectangle();
435 * Mark a component as dirty over its entire bounds.
437 * @param component The component to mark as dirty
439 * @see #dirtyComponents
440 * @see #addDirtyRegion
441 * @see #getDirtyRegion
442 * @see #isCompletelyDirty
443 * @see #markCompletelyClean
445 public void markCompletelyDirty(JComponent component)
447 Rectangle r = component.getBounds();
448 addDirtyRegion(component, 0, 0, r.width, r.height);
452 * Remove all dirty regions for a specified component
454 * @param component The component to mark as clean
456 * @see #dirtyComponents
457 * @see #addDirtyRegion
458 * @see #getDirtyRegion
459 * @see #isCompletelyDirty
460 * @see #markCompletelyDirty
462 public void markCompletelyClean(JComponent component)
464 synchronized (dirtyComponents)
466 dirtyComponents.remove(component);
471 * Return <code>true</code> if the specified component is completely
472 * contained within its dirty region, otherwise <code>false</code>
474 * @param component The component to check for complete dirtyness
476 * @return Whether the component is completely dirty
478 * @see #dirtyComponents
479 * @see #addDirtyRegion
480 * @see #getDirtyRegion
481 * @see #isCompletelyDirty
482 * @see #markCompletelyClean
484 public boolean isCompletelyDirty(JComponent component)
486 boolean retVal = false;
487 if (dirtyComponents.containsKey(component))
489 Rectangle dirtyRegion = (Rectangle) dirtyComponents.get(component);
490 retVal = dirtyRegion.equals(SwingUtilities.getLocalBounds(component));
496 * Validate all components which have been marked invalid in the {@link
497 * #invalidComponents} vector.
499 public void validateInvalidComponents()
501 // We don't use an iterator here because that would fail when there are
502 // components invalidated during the validation of others, which happens
503 // quite frequently. Instead we synchronize the access a little more.
504 while (invalidComponents.size() > 0)
507 synchronized (invalidComponents)
509 comp = (Component) invalidComponents.remove(0);
511 // Validate the validate component.
512 if (! (comp.isVisible() && comp.isShowing()))
519 * Repaint all regions of all components which have been marked dirty in the
520 * {@link #dirtyComponents} table.
522 public void paintDirtyRegions()
524 // Short cicuit if there is nothing to paint.
525 if (dirtyComponents.size() == 0)
528 // Swap dirtyRegions with dirtyRegionsWork to avoid locking.
529 synchronized (dirtyComponents)
531 HashMap swap = dirtyComponents;
532 dirtyComponents = dirtyComponentsWork;
533 dirtyComponentsWork = swap;
536 // Compile a set of repaint roots.
537 HashSet repaintRoots = new HashSet();
538 Set components = dirtyComponentsWork.keySet();
539 for (Iterator i = components.iterator(); i.hasNext();)
541 JComponent dirty = (JComponent) i.next();
542 compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots);
545 repaintUnderway = true;
546 for (Iterator i = repaintRoots.iterator(); i.hasNext();)
548 JComponent comp = (JComponent) i.next();
549 Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp);
550 if (damaged == null || damaged.isEmpty())
552 comp.paintImmediately(damaged);
554 dirtyComponentsWork.clear();
555 repaintUnderway = false;
556 commitRemainingBuffers();
560 * Compiles a list of components that really get repainted. This is called
561 * once for each component in the dirtyComponents HashMap, each time with
562 * another <code>dirty</code> parameter. This searches up the component
563 * hierarchy of <code>dirty</code> to find the highest parent that is also
564 * marked dirty and merges the dirty regions.
566 * @param dirtyRegions the dirty regions
567 * @param dirty the component for which to find the repaint root
568 * @param roots the list to which new repaint roots get appended
570 private void compileRepaintRoots(HashMap dirtyRegions, JComponent dirty,
573 Component current = dirty;
574 Component root = dirty;
576 // Search the highest component that is also marked dirty.
580 parent = current.getParent();
581 if (parent == null || !(parent instanceof JComponent))
585 // We can skip to the next up when this parent is not dirty.
586 if (dirtyRegions.containsKey(parent))
592 // Merge the rectangles of the root and the requested component if
593 // the are different.
596 Rectangle dirtyRect = (Rectangle) dirtyRegions.get(dirty);
597 dirtyRect = SwingUtilities.convertRectangle(dirty, dirtyRect, root);
598 Rectangle rootRect = (Rectangle) dirtyRegions.get(root);
599 SwingUtilities.computeUnion(dirtyRect.x, dirtyRect.y, dirtyRect.width,
600 dirtyRect.height, rootRect);
603 // Adds the root to the roots set.
608 * Get an offscreen buffer for painting a component's image. This image
609 * may be smaller than the proposed dimensions, depending on the value of
610 * the {@link #doubleBufferMaximumSize} property.
612 * @param component The component to return an offscreen buffer for
613 * @param proposedWidth The proposed width of the offscreen buffer
614 * @param proposedHeight The proposed height of the offscreen buffer
616 * @return A shared offscreen buffer for painting
618 public Image getOffscreenBuffer(Component component, int proposedWidth,
621 Component root = getRoot(component);
622 Image buffer = (Image) offscreenBuffers.get(root);
624 || buffer.getWidth(null) < proposedWidth
625 || buffer.getHeight(null) < proposedHeight)
627 int width = Math.max(proposedWidth, root.getWidth());
628 width = Math.min(doubleBufferMaximumSize.width, width);
629 int height = Math.max(proposedHeight, root.getHeight());
630 height = Math.min(doubleBufferMaximumSize.height, height);
631 buffer = component.createImage(width, height);
632 offscreenBuffers.put(root, buffer);
638 * Gets the root of the component given. If a parent of the
639 * component is an instance of Applet, then the applet is
640 * returned. The applet is considered the root for painting.
641 * Otherwise, the root Window is returned if it exists.
643 * @param comp - The component to get the root for.
644 * @return the parent root. An applet if it is a parent,
645 * or the root window. If neither exist, null is returned.
647 private Component getRoot(Component comp)
653 if (app == null && comp instanceof Window)
655 else if (comp instanceof Applet)
657 comp = comp.getParent();
664 * Blits the back buffer of the specified root component to the screen. If
665 * the RepaintManager is currently working on a paint request, the commit
666 * requests are queued up and committed at once when the paint request is
667 * done (by {@link #commitRemainingBuffers}). This is package private because
668 * it must get called by JComponent.
670 * @param root the component, either a Window or an Applet instance
671 * @param area the area to paint on screen
673 void commitBuffer(Component root, Rectangle area)
675 // We synchronize on dirtyComponents here because that is what
676 // paintDirtyRegions also synchronizes on while painting.
677 synchronized (dirtyComponents)
679 // If the RepaintManager is not currently painting, then directly
680 // blit the requested buffer on the screen.
681 if (! repaintUnderway)
683 Graphics g = root.getGraphics();
684 Image buffer = (Image) offscreenBuffers.get(root);
685 Rectangle clip = g.getClipBounds();
687 area = SwingUtilities.computeIntersection(clip.x, clip.y,
688 clip.width, clip.height,
692 int dx2 = area.x + area.width;
693 int dy2 = area.y + area.height;
694 // Make sure we have a sane clip at this point.
695 g.clipRect(area.x, area.y, area.width, area.height);
697 // Make sure the coordinates are inside the buffer, everything else
698 // might lead to problems.
699 // TODO: This code should not really be necessary, however, in fact
700 // we have two issues here:
701 // 1. We shouldn't get repaint requests in areas outside the buffer
702 // region in the first place. This still happens for example
703 // when a component is inside a JViewport, and the component has
704 // a size that would reach beyond the window size.
705 // 2. Graphics.drawImage() should not behave strange when trying
706 // to draw regions outside the image.
707 int bufferWidth = buffer.getWidth(root);
708 int bufferHeight = buffer.getHeight(root);
709 dx1 = Math.min(bufferWidth, dx1);
710 dy1 = Math.min(bufferHeight, dy1);
711 dx2 = Math.min(bufferWidth, dx2);
712 dy2 = Math.min(bufferHeight, dy2);
713 g.drawImage(buffer, 0, 0, root);
716 // Otherwise queue this request up, until all the RepaintManager work
720 if (commitRequests.containsKey(root))
721 SwingUtilities.computeUnion(area.x, area.y, area.width,
723 (Rectangle) commitRequests.get(root));
725 commitRequests.put(root, area);
731 * Commits the queued up back buffers to screen all at once.
733 private void commitRemainingBuffers()
735 // We synchronize on dirtyComponents here because that is what
736 // paintDirtyRegions also synchronizes on while painting.
737 synchronized (dirtyComponents)
739 Set entrySet = commitRequests.entrySet();
740 Iterator i = entrySet.iterator();
743 Map.Entry entry = (Map.Entry) i.next();
744 Component root = (Component) entry.getKey();
745 Rectangle area = (Rectangle) entry.getValue();
746 commitBuffer(root, area);
753 * Creates and returns a volatile offscreen buffer for the specified
754 * component that can be used as a double buffer. The returned image
755 * is a {@link VolatileImage}. Its size will be <code>(proposedWidth,
756 * proposedHeight)</code> except when the maximum double buffer size
757 * has been set in this RepaintManager.
759 * @param comp the Component for which to create a volatile buffer
760 * @param proposedWidth the proposed width of the buffer
761 * @param proposedHeight the proposed height of the buffer
767 public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
770 Component root = getRoot(comp);
771 Image buffer = (Image) offscreenBuffers.get(root);
773 || buffer.getWidth(null) < proposedWidth
774 || buffer.getHeight(null) < proposedHeight
775 || !(buffer instanceof VolatileImage))
777 int width = Math.max(proposedWidth, root.getWidth());
778 width = Math.min(doubleBufferMaximumSize.width, width);
779 int height = Math.max(proposedHeight, root.getHeight());
780 height = Math.min(doubleBufferMaximumSize.height, height);
781 buffer = root.createVolatileImage(width, height);
783 offscreenBuffers.put(root, buffer);
790 * Get the value of the {@link #doubleBufferMaximumSize} property.
792 * @return The current value of the property
794 * @see #setDoubleBufferMaximumSize
796 public Dimension getDoubleBufferMaximumSize()
798 return doubleBufferMaximumSize;
802 * Set the value of the {@link #doubleBufferMaximumSize} property.
804 * @param size The new value of the property
806 * @see #getDoubleBufferMaximumSize
808 public void setDoubleBufferMaximumSize(Dimension size)
810 doubleBufferMaximumSize = size;
814 * Set the value of the {@link #doubleBufferingEnabled} property.
816 * @param buffer The new value of the property
818 * @see #isDoubleBufferingEnabled
820 public void setDoubleBufferingEnabled(boolean buffer)
822 doubleBufferingEnabled = buffer;
826 * Get the value of the {@link #doubleBufferingEnabled} property.
828 * @return The current value of the property
830 * @see #setDoubleBufferingEnabled
832 public boolean isDoubleBufferingEnabled()
834 return doubleBufferingEnabled;
837 public String toString()
839 return "RepaintManager";