/* * @(#)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; } }