/* * @(#)RepaintManager.java 1.60 04/02/18 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.swing; import java.awt.*; import java.awt.event.*; import java.awt.peer.ComponentPeer; import java.awt.peer.ContainerPeer; import java.awt.image.VolatileImage; import java.util.*; import java.applet.*; import sun.security.action.GetPropertyAction; /** * This class manages repaint requests, allowing the number * of repaints to be minimized, for example by collapsing multiple * requests into a single repaint for members of a component tree. * * @version 1.60 02/18/04 * @author Arnaud Weber */ public class RepaintManager { /** * Maps from GraphicsConfiguration to VolatileImage. */ private Map volatileMap = new HashMap(1); Hashtable dirtyComponents = new Hashtable(); Hashtable tmpDirtyComponents = new Hashtable(); Vector invalidComponents; boolean doubleBufferingEnabled = true; private Dimension doubleBufferMaxSize; // Support for both the standard and volatile offscreen buffers exists to // provide backwards compatibility for the [rare] programs which may be // calling getOffScreenBuffer() and not expecting to get a VolatileImage. // Swing internally is migrating to use *only* the volatile image buffer. // Support for standard offscreen buffer // DoubleBufferInfo standardDoubleBuffer; private static final Object repaintManagerKey = RepaintManager.class; // Whether or not a VolatileImage should be used for double-buffered painting static boolean volatileImageBufferEnabled = true; // The maximum number of times Swing will attempt to use the VolatileImage // buffer during a paint operation. static final int VOLATILE_LOOP_MAX = 2; static { String vib = (String) java.security.AccessController.doPrivileged( new GetPropertyAction("swing.volatileImageBufferEnabled")); volatileImageBufferEnabled = (vib == null || vib.equals("true")); } /** * Return the RepaintManager for the calling thread given a Component. * * @param c a Component -- unused in the default implementation, but could * be used by an overridden version to return a different RepaintManager * depending on the Component * @return the RepaintManager object */ public static RepaintManager currentManager(Component c) { // Note: SystemEventQueueUtilities.ComponentWorkRequest passes // in null as the component, so if component is ever used to // determine the current RepaintManager, SystemEventQueueUtilities // will need to be modified accordingly. RepaintManager result = (RepaintManager) SwingUtilities.appContextGet(repaintManagerKey); if(result == null) { result = new RepaintManager(); SwingUtilities.appContextPut(repaintManagerKey, result); } return result; } /** * Return the RepaintManager for the calling thread given a JComponent. *
* Note: This method exists for backward binary compatibility with earlier
* versions of the Swing library. It simply returns the result returned by
* {@link #currentManager(Component)}.
*
* @param c a JComponent -- unused
* @return the RepaintManager object
*/
public static RepaintManager currentManager(JComponent c) {
return currentManager((Component)c);
}
/**
* Set the RepaintManager that should be used for the calling
* thread. aRepaintManager will become the current RepaintManager
* for the calling thread's thread group.
* @param aRepaintManager the RepaintManager object to use
*/
public static void setCurrentManager(RepaintManager aRepaintManager) {
if (aRepaintManager != null) {
SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
} else {
SwingUtilities.appContextRemove(repaintManagerKey);
}
}
/**
* Create a new RepaintManager instance. You rarely call this constructor.
* directly. To get the default RepaintManager, use
* RepaintManager.currentManager(JComponent) (normally "this").
*/
public RepaintManager() {
Object dbe = java.security.AccessController.doPrivileged(
new GetPropertyAction("awt.nativeDoubleBuffering"));
boolean nativeDoubleBuffering = (dbe != null) ?
Boolean.valueOf(dbe.toString()).booleanValue() : false;
// If native doublebuffering is being used, do NOT use
// Swing doublebuffering.
doubleBufferingEnabled = !nativeDoubleBuffering;
}
/**
* Mark the component as in need of layout and queue a runnable
* for the event dispatching thread that will validate the components
* first isValidateRoot() ancestor.
*
* @see JComponent#isValidateRoot
* @see #removeInvalidComponent
*/
public synchronized void addInvalidComponent(JComponent invalidComponent)
{
Component validateRoot = null;
/* Find the first JComponent ancestor of this component whose
* isValidateRoot() method returns true.
*/
for(Component c = invalidComponent; c != null; c = c.getParent()) {
if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
return;
}
if ((c instanceof JComponent) && (((JComponent)c).isValidateRoot())) {
validateRoot = c;
break;
}
}
/* There's no validateRoot to apply validate to, so we're done.
*/
if (validateRoot == null) {
return;
}
/* If the validateRoot and all of its ancestors aren't visible
* then we don't do anything. While we're walking up the tree
* we find the root Window or Applet.
*/
Component root = null;
for(Component c = validateRoot; c != null; c = c.getParent()) {
if (!c.isVisible() || (c.getPeer() == null)) {
return;
}
if ((c instanceof Window) || (c instanceof Applet)) {
root = c;
break;
}
}
if (root == null) {
return;
}
/* Lazily create the invalidateComponents vector and add the
* validateRoot if it's not there already. If this validateRoot
* is already in the vector, we're done.
*/
if (invalidComponents == null) {
invalidComponents = new Vector();
}
else {
int n = invalidComponents.size();
for(int i = 0; i < n; i++) {
if(validateRoot == (Component)(invalidComponents.elementAt(i))) {
return;
}
}
}
invalidComponents.addElement(validateRoot);
/* Queues a Runnable that calls RepaintManager.validateInvalidComponents()
* and RepaintManager.paintDirtyRegions() with SwingUtilities.invokeLater().
*/
SystemEventQueueUtilities.queueComponentWorkRequest(root);
}
/**
* Remove a component from the list of invalid components.
*
* @see #addInvalidComponent
*/
public synchronized void removeInvalidComponent(JComponent component) {
if(invalidComponents != null) {
int index = invalidComponents.indexOf(component);
if(index != -1) {
invalidComponents.removeElementAt(index);
}
}
}
/**
* Add a component in the list of components that should be refreshed.
* If c already has a dirty region, the rectangle (x,y,w,h)
* will be unioned with the region that should be redrawn.
*
* @see JComponent#repaint
*/
public void addDirtyRegion(JComponent c, int x, int y, int w, int h)
{
/* Special cases we don't have to bother with.
*/
if ((w <= 0) || (h <= 0) || (c == null)) {
return;
}
if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {
return;
}
if (extendDirtyRegion(c, x, y, w, h)) {
// Component was already marked as dirty, region has been
// extended, no need to continue.
return;
}
/* Make sure that c and all it ancestors (up to an Applet or
* Window) are visible. This loop has the same effect as
* checking c.isShowing() (and note that it's still possible
* that c is completely obscured by an opaque ancestor in
* the specified rectangle).
*/
Component root = null;
// Note: We can't synchronize around this, Frame.getExtendedState
// is synchronized so that if we were to synchronize around this
// it could lead to the possibility of getting locks out
// of order and deadlocking.
for (Container p = c; p != null; p = p.getParent()) {
if (!p.isVisible() || (p.getPeer() == null)) {
return;
}
if ((p instanceof Window) || (p instanceof Applet)) {
// Iconified frames are still visible!
if (p instanceof Frame &&
(((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
Frame.ICONIFIED) {
return;
}
root = p;
break;
}
}
if (root == null) return;
synchronized(this) {
if (extendDirtyRegion(c, x, y, w, h)) {
// In between last check and this check another thread
// queued up runnable, can bail here.
return;
}
dirtyComponents.put(c, new Rectangle(x, y, w, h));
}
/* Queues a Runnable that calls validateInvalidComponents() and
* rm.paintDirtyRegions() with SwingUtilities.invokeLater().
*/
SystemEventQueueUtilities.queueComponentWorkRequest(root);
}
/**
* Extends the dirty region for the specified component to include
* the new region.
*
* @return false if c
is not yet marked dirty.
*/
private synchronized boolean extendDirtyRegion(
Component c, int x, int y, int w, int h) {
Rectangle r = (Rectangle)dirtyComponents.get(c);
if (r != null) {
// A non-null r implies c is already marked as dirty,
// and that the parent is valid. Therefore we can
// just union the rect and bail.
SwingUtilities.computeUnion(x, y, w, h, r);
return true;
}
return false;
}
/** Return the current dirty region for a component.
* Return an empty rectangle if the component is not
* dirty.
*/
public Rectangle getDirtyRegion(JComponent aComponent) {
Rectangle r = null;
synchronized(this) {
r = (Rectangle)dirtyComponents.get(aComponent);
}
if(r == null)
return new Rectangle(0,0,0,0);
else
return new Rectangle(r);
}
/**
* Mark a component completely dirty. aComponent will be
* completely painted during the next paintDirtyRegions() call.
*/
public void markCompletelyDirty(JComponent aComponent) {
addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
}
/**
* Mark a component completely clean. aComponent will not
* get painted during the next paintDirtyRegions() call.
*/
public void markCompletelyClean(JComponent aComponent) {
synchronized(this) {
dirtyComponents.remove(aComponent);
}
}
/**
* Convenience method that returns true if aComponent will be completely
* painted during the next paintDirtyRegions(). If computing dirty regions is
* expensive for your component, use this method and avoid computing dirty region
* if it return true.
*/
public boolean isCompletelyDirty(JComponent aComponent) {
Rectangle r;
r = getDirtyRegion(aComponent);
if(r.width == Integer.MAX_VALUE &&
r.height == Integer.MAX_VALUE)
return true;
else
return false;
}
/**
* Validate all of the components that have been marked invalid.
* @see #addInvalidComponent
*/
public void validateInvalidComponents() {
Vector ic;
synchronized(this) {
if(invalidComponents == null) {
return;
}
ic = invalidComponents;
invalidComponents = null;
}
int n = ic.size();
for(int i = 0; i < n; i++) {
((Component)ic.elementAt(i)).validate();
}
}
/**
* Paint all of the components that have been marked dirty.
*
* @see #addDirtyRegion
*/
public void paintDirtyRegions() {
int i, count;
Vector roots;
JComponent dirtyComponent;
synchronized(this) { // swap for thread safety
Hashtable tmp = tmpDirtyComponents;
tmpDirtyComponents = dirtyComponents;
dirtyComponents = tmp;
dirtyComponents.clear();
}
count = tmpDirtyComponents.size();
if (count == 0) {
return;
}
Rectangle rect;
int localBoundsX = 0;
int localBoundsY = 0;
int localBoundsH = 0;
int localBoundsW = 0;
Enumeration keys;
roots = new Vector(count);
keys = tmpDirtyComponents.keys();
while(keys.hasMoreElements()) {
dirtyComponent = (JComponent) keys.nextElement();
collectDirtyComponents(tmpDirtyComponents, dirtyComponent, roots);
}
count = roots.size();
// System.out.println("roots size is " + count);
for(i=0 ; i < count ; i++) {
dirtyComponent = (JComponent) roots.elementAt(i);
rect = (Rectangle) tmpDirtyComponents.get(dirtyComponent);
// System.out.println("Should refresh :" + rect);
localBoundsH = dirtyComponent.getHeight();
localBoundsW = dirtyComponent.getWidth();
SwingUtilities.computeIntersection(localBoundsX,
localBoundsY,
localBoundsW,
localBoundsH,
rect);
// System.out.println("** paint of " + dirtyComponent + rect);
if (rect.x == 0 && rect.y == 0 &&
rect.width == dirtyComponent.getWidth() &&
rect.height == dirtyComponent.getHeight()) {
Container parent = dirtyComponent.getParent();
ComponentPeer parentPeer;
if (parent != null && !parent.isLightweight() &&
(parentPeer = parent.getPeer()) != null) {
// Cancel any pending paints on the heavy weight peer.
// This avoid duplicate painting.
((ContainerPeer)parentPeer).cancelPendingPaint(
dirtyComponent.getX(),
dirtyComponent.getY(),
rect.width, rect.height);
}
}
dirtyComponent.paintImmediately(rect.x,rect.y,rect.width,rect.height);
}
tmpDirtyComponents.clear();
}
Rectangle tmp = new Rectangle();
void collectDirtyComponents(Hashtable dirtyComponents,
JComponent dirtyComponent,
Vector roots) {
int dx, dy, rootDx, rootDy;
Component component, rootDirtyComponent,parent;
//Rectangle tmp;
Rectangle cBounds;
// Find the highest parent which is dirty. When we get out of this
// rootDx and rootDy will contain the translation from the
// rootDirtyComponent's coordinate system to the coordinates of the
// original dirty component. The tmp Rect is also used to compute the
// visible portion of the dirtyRect.
component = rootDirtyComponent = dirtyComponent;
int x = dirtyComponent.getX();
int y = dirtyComponent.getY();
int w = dirtyComponent.getWidth();
int h = dirtyComponent.getHeight();
dx = rootDx = 0;
dy = rootDy = 0;
tmp.setBounds((Rectangle) dirtyComponents.get(dirtyComponent));
// System.out.println("Collect dirty component for bound " + tmp +
// "component bounds is " + cBounds);;
SwingUtilities.computeIntersection(0,0,w,h,tmp);
if (tmp.isEmpty()) {
// System.out.println("Empty 1");
return;
}
for(;;) {
parent = component.getParent();
if(parent == null)
break;
if(!(parent instanceof JComponent))
break;
component = parent;
dx += x;
dy += y;
tmp.setLocation(tmp.x + x, tmp.y + y);
x = component.getX();
y = component.getY();
w = component.getWidth();
h = component.getHeight();
tmp = SwingUtilities.computeIntersection(0,0,w,h,tmp);
if (tmp.isEmpty()) {
// System.out.println("Empty 2");
return;
}
if (dirtyComponents.get(component) != null) {
rootDirtyComponent = component;
rootDx = dx;
rootDy = dy;
}
}
if (dirtyComponent != rootDirtyComponent) {
Rectangle r;
tmp.setLocation(tmp.x + rootDx - dx,
tmp.y + rootDy - dy);
r = (Rectangle)dirtyComponents.get(rootDirtyComponent);
SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r);
}
// If we haven't seen this root before, then we need to add it to the
// list of root dirty Views.
if (!roots.contains(rootDirtyComponent))
roots.addElement(rootDirtyComponent);
}
/**
* Returns a string that displays and identifies this
* object's properties.
*
* @return a String representation of this object
*/
public synchronized String toString() {
StringBuffer sb = new StringBuffer();
if(dirtyComponents != null)
sb.append("" + dirtyComponents);
return sb.toString();
}
/**
* Return the offscreen buffer that should be used as a double buffer with
* the component c
.
* By default there is a double buffer per RepaintManager.
* The buffer might be smaller than (proposedWidth,proposedHeight)
* This happens when the maximum double buffer size as been set for the receiving
* repaint manager.
*/
public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
}
/**
* Return a volatile offscreen buffer that should be used as a
* double buffer with the specified component c
.
* The image returned will be an instance of VolatileImage, or null
* if a VolatileImage object could not be instantiated.
* This buffer might be smaller than (proposedWidth,proposedHeight)
.
* This happens when the maximum double buffer size has been set for this
* repaint manager.
*
* @see java.awt.image.VolatileImage
* @since 1.4
*/
public Image getVolatileOffscreenBuffer(Component c,
int proposedWidth,int proposedHeight) {
GraphicsConfiguration config = c.getGraphicsConfiguration();
if (config == null) {
config = GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
}
Dimension maxSize = getDoubleBufferMaximumSize();
int width = proposedWidth < 1 ? 1 :
(proposedWidth > maxSize.width? maxSize.width : proposedWidth);
int height = proposedHeight < 1 ? 1 :
(proposedHeight > maxSize.height? maxSize.height : proposedHeight);
VolatileImage image = (VolatileImage)volatileMap.get(config);
if (image == null || image.getWidth() < width ||
image.getHeight() < height) {
if (image != null) {
image.flush();
}
image = config.createCompatibleVolatileImage(width, height);
volatileMap.put(config, image);
}
return image;
}
private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
Dimension maxSize = getDoubleBufferMaximumSize();
DoubleBufferInfo doubleBuffer = null;
int width, height;
if (standardDoubleBuffer == null) {
standardDoubleBuffer = new DoubleBufferInfo();
}
doubleBuffer = standardDoubleBuffer;
width = proposedWidth < 1? 1 :
(proposedWidth > maxSize.width? maxSize.width : proposedWidth);
height = proposedHeight < 1? 1 :
(proposedHeight > maxSize.height? maxSize.height : proposedHeight);
if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
(doubleBuffer.size.width < width ||
doubleBuffer.size.height < height))) {
doubleBuffer.needsReset = false;
if (doubleBuffer.image != null) {
doubleBuffer.image.flush();
doubleBuffer.image = null;
}
width = Math.max(doubleBuffer.size.width, width);
height = Math.max(doubleBuffer.size.height, height);
}
Image result = doubleBuffer.image;
if (doubleBuffer.image == null) {
result = c.createImage(width , height);
doubleBuffer.size = new Dimension(width, height);
if (c instanceof JComponent) {
((JComponent)c).setCreatedDoubleBuffer(true);
doubleBuffer.image = result;
}
// JComponent will inform us when it is no longer valid
// (via removeNotify) we have no such hook to other components,
// therefore we don't keep a ref to the Component
// (indirectly through the Image) by stashing the image.
}
return result;
}
/** Set the maximum double buffer size. **/
public void setDoubleBufferMaximumSize(Dimension d) {
doubleBufferMaxSize = d;
if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
if (standardDoubleBuffer.image.getWidth(null) > d.width ||
standardDoubleBuffer.image.getHeight(null) > d.height) {
standardDoubleBuffer.image = null;
}
}
// Clear out the VolatileImages
Iterator gcs = volatileMap.keySet().iterator();
while (gcs.hasNext()) {
GraphicsConfiguration gc = (GraphicsConfiguration)gcs.next();
VolatileImage image = (VolatileImage)volatileMap.get(gc);
if (image.getWidth() > d.width || image.getHeight() > d.height) {
image.flush();
gcs.remove();
}
}
}
/**
* Returns the maximum double buffer size.
*
* @return a Dimension object representing the maximum size
*/
public Dimension getDoubleBufferMaximumSize() {
if (doubleBufferMaxSize == null) {
try {
doubleBufferMaxSize = Toolkit.getDefaultToolkit().getScreenSize();
} catch (HeadlessException e) {
doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
}
return doubleBufferMaxSize;
}
/**
* Enables or disables double buffering in this RepaintManager.
* CAUTION: The default value for this property is set for optimal
* paint performance on the given platform and it is not recommended
* that programs modify this property directly.
*
* @param aFlag true to activate double buffering
* @see #isDoubleBufferingEnabled
*/
public void setDoubleBufferingEnabled(boolean aFlag) {
doubleBufferingEnabled = aFlag;
}
/**
* Returns true if this RepaintManager is double buffered.
* The default value for this property may vary from platform
* to platform. On platforms where native double buffering
* is supported in the AWT, the default value will be false
* to avoid unnecessary buffering in Swing.
* On platforms where native double buffering is not supported,
* the default value will be true
.
*
* @return true if this object is double buffered
*/
public boolean isDoubleBufferingEnabled() {
return doubleBufferingEnabled;
}
/**
* This resets the double buffer. Actually, it marks the double buffer
* as invalid, the double buffer will then be recreated on the next
* invocation of getOffscreenBuffer.
*/
void resetDoubleBuffer() {
if (standardDoubleBuffer != null) {
standardDoubleBuffer.needsReset = true;
}
}
/**
* This resets the volatile double buffer.
*/
void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
Image image = (Image)volatileMap.remove(gc);
if (image != null) {
image.flush();
}
}
/**
* Returns true if we should use the Image
returned
* from getVolatileOffscreenBuffer
to do double buffering.
*/
boolean useVolatileDoubleBuffer() {
return volatileImageBufferEnabled;
}
private class DoubleBufferInfo {
public Image image;
public Dimension size;
public boolean needsReset = false;
}
}