summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/RepaintManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/RepaintManager.java')
-rw-r--r--libjava/classpath/javax/swing/RepaintManager.java392
1 files changed, 243 insertions, 149 deletions
diff --git a/libjava/classpath/javax/swing/RepaintManager.java b/libjava/classpath/javax/swing/RepaintManager.java
index 0be81053dc5..ed0500992c5 100644
--- a/libjava/classpath/javax/swing/RepaintManager.java
+++ b/libjava/classpath/javax/swing/RepaintManager.java
@@ -40,14 +40,18 @@ package javax.swing;
import java.awt.Component;
import java.awt.Dimension;
+import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
+import java.awt.Window;
import java.awt.image.VolatileImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
import java.util.WeakHashMap;
/**
@@ -62,6 +66,7 @@ import java.util.WeakHashMap;
* href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
* document</a> for more details.</p>
*
+ * @author Roman Kennke (kennke@aicas.com)
* @author Graydon Hoare (graydon@redhat.com)
*/
public class RepaintManager
@@ -69,8 +74,13 @@ public class RepaintManager
/**
* The current repaint managers, indexed by their ThreadGroups.
*/
- static WeakHashMap currentRepaintManagers;
-
+ private static WeakHashMap currentRepaintManagers;
+
+ /**
+ * A rectangle object to be reused in damaged regions calculation.
+ */
+ private static Rectangle rectCache = new Rectangle();
+
/**
* <p>A helper class which is placed into the system event queue at
* various times in order to facilitate repainting and layout. There is
@@ -84,7 +94,7 @@ public class RepaintManager
* swing paint thread, which revalidates all invalid components and
* repaints any damage in the swing scene.</p>
*/
- protected class RepaintWorker
+ private class RepaintWorker
implements Runnable
{
@@ -107,12 +117,18 @@ public class RepaintManager
public void run()
{
- ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
- RepaintManager rm =
- (RepaintManager) currentRepaintManagers.get(threadGroup);
- setLive(false);
- rm.validateInvalidComponents();
- rm.paintDirtyRegions();
+ try
+ {
+ ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
+ RepaintManager rm =
+ (RepaintManager) currentRepaintManagers.get(threadGroup);
+ rm.validateInvalidComponents();
+ rm.paintDirtyRegions();
+ }
+ finally
+ {
+ setLive(false);
+ }
}
}
@@ -135,41 +151,23 @@ public class RepaintManager
* @param o1 the first component
* @param o2 the second component
*
- * @return a negative integer, if <code>o1</code> is higher in the
- * hierarchy than <code>o2</code>, zero, if both are at the same
- * level and a positive integer, if <code>o1</code> is deeper in
- * the hierarchy than <code>o2</code>
+ * @return a negative integer, if <code>o1</code> is bigger in than
+ * <code>o2</code>, zero, if both are at the same size and a
+ * positive integer, if <code>o1</code> is smaller than
+ * <code>o2</code>
*/
public int compare(Object o1, Object o2)
{
if (o1 instanceof JComponent && o2 instanceof JComponent)
{
JComponent c1 = (JComponent) o1;
+ Rectangle d1 = (Rectangle) dirtyComponents.get(c1);
JComponent c2 = (JComponent) o2;
- return getDepth(c1) - getDepth(c2);
- }
- else
- throw new ClassCastException("This comparator can only be used with "
- + "JComponents");
- }
-
- /**
- * Computes the depth for a given JComponent.
- *
- * @param c the component to compute the depth for
- *
- * @return the depth of the component
- */
- private int getDepth(JComponent c)
- {
- Component comp = c;
- int depth = 0;
- while (comp != null)
- {
- comp = comp.getParent();
- depth++;
+ Rectangle d2 = (Rectangle) dirtyComponents.get(c2);
+ return d2.width * d2.height - d1.width * d1.height;
}
- return depth;
+ throw new ClassCastException("This comparator can only be used with "
+ + "JComponents");
}
}
@@ -179,6 +177,9 @@ public class RepaintManager
* to exactly one rectangle. When more regions are marked as dirty on a
* component, they are union'ed with the existing rectangle.
*
+ * This is package private to avoid a synthetic accessor method in inner
+ * class.
+ *
* @see #addDirtyRegion
* @see #getDirtyRegion
* @see #isCompletelyDirty
@@ -187,18 +188,10 @@ public class RepaintManager
*/
HashMap dirtyComponents;
- HashMap workDirtyComponents;
-
- /**
- * Stores the order in which the components get repainted.
- */
- ArrayList repaintOrder;
- ArrayList workRepaintOrder;
-
/**
* The comparator used for ordered inserting into the repaintOrder list.
*/
- Comparator comparator;
+ private transient Comparator comparator;
/**
* A single, shared instance of the helper class. Any methods which mark
@@ -209,7 +202,7 @@ public class RepaintManager
* @see #addDirtyRegion
* @see #addInvalidComponent
*/
- RepaintWorker repaintWorker;
+ private RepaintWorker repaintWorker;
/**
* The set of components which need revalidation, in the "layout" sense.
@@ -221,8 +214,7 @@ public class RepaintManager
* @see #removeInvalidComponent
* @see #validateInvalidComponents
*/
- ArrayList invalidComponents;
- ArrayList workInvalidComponents;
+ private ArrayList invalidComponents;
/**
* Whether or not double buffering is enabled on this repaint
@@ -232,17 +224,27 @@ public class RepaintManager
* @see #isDoubleBufferingEnabled
* @see #setDoubleBufferingEnabled
*/
- boolean doubleBufferingEnabled;
+ private boolean doubleBufferingEnabled;
- /**
- * The current offscreen buffer. This is reused for all requests for
- * offscreen drawing buffers. It grows as necessary, up to {@link
- * #doubleBufferMaximumSize}, but there is only one shared instance.
- *
- * @see #getOffscreenBuffer
- * @see #doubleBufferMaximumSize
+ /**
+ * The offscreen buffers. This map holds one offscreen buffer per
+ * Window/Applet and releases them as soon as the Window/Applet gets garbage
+ * collected.
*/
- Image doubleBuffer;
+ private WeakHashMap offscreenBuffers;
+
+ /**
+ * Indicates if the RepaintManager is currently repainting an area.
+ */
+ private boolean repaintUnderway;
+
+ /**
+ * This holds buffer commit requests when the RepaintManager is working.
+ * This maps Component objects (the top level components) to Rectangle
+ * objects (the area of the corresponding buffer that must be blitted on
+ * the component).
+ */
+ private HashMap commitRequests;
/**
* The maximum width and height to allocate as a double buffer. Requests
@@ -252,7 +254,7 @@ public class RepaintManager
* @see #getDoubleBufferMaximumSize
* @see #setDoubleBufferMaximumSize
*/
- Dimension doubleBufferMaximumSize;
+ private Dimension doubleBufferMaximumSize;
/**
@@ -261,14 +263,13 @@ public class RepaintManager
public RepaintManager()
{
dirtyComponents = new HashMap();
- workDirtyComponents = new HashMap();
- repaintOrder = new ArrayList();
- workRepaintOrder = new ArrayList();
invalidComponents = new ArrayList();
- workInvalidComponents = new ArrayList();
repaintWorker = new RepaintWorker();
doubleBufferMaximumSize = new Dimension(2000,2000);
doubleBufferingEnabled = true;
+ offscreenBuffers = new WeakHashMap();
+ repaintUnderway = false;
+ commitRequests = new HashMap();
}
/**
@@ -346,9 +347,9 @@ public class RepaintManager
*
* @see #removeInvalidComponent
*/
- public synchronized void addInvalidComponent(JComponent component)
+ public void addInvalidComponent(JComponent component)
{
- Component ancestor = component.getParent();
+ Component ancestor = component;
while (ancestor != null
&& (! (ancestor instanceof JComponent)
@@ -363,8 +364,11 @@ public class RepaintManager
if (invalidComponents.contains(component))
return;
- invalidComponents.add(component);
-
+ synchronized (invalidComponents)
+ {
+ invalidComponents.add(component);
+ }
+
if (! repaintWorker.isLive())
{
repaintWorker.setLive(true);
@@ -379,9 +383,12 @@ public class RepaintManager
*
* @see #addInvalidComponent
*/
- public synchronized void removeInvalidComponent(JComponent component)
+ public void removeInvalidComponent(JComponent component)
{
- invalidComponents.remove(component);
+ synchronized (invalidComponents)
+ {
+ invalidComponents.remove(component);
+ }
}
/**
@@ -402,41 +409,40 @@ public class RepaintManager
* @see #markCompletelyClean
* @see #markCompletelyDirty
*/
- public synchronized void addDirtyRegion(JComponent component, int x, int y,
- int w, int h)
+ public void addDirtyRegion(JComponent component, int x, int y,
+ int w, int h)
{
- if (w == 0 || h == 0 || !component.isShowing())
+ if (w <= 0 || h <= 0 || !component.isShowing())
return;
- Rectangle r = new Rectangle(x, y, w, h);
- if (dirtyComponents.containsKey(component))
- r = r.union((Rectangle)dirtyComponents.get(component));
- else
- insertInRepaintOrder(component);
- dirtyComponents.put(component, r);
- if (! repaintWorker.isLive())
+
+ component.computeVisibleRect(rectCache);
+ SwingUtilities.computeIntersection(x, y, w, h, rectCache);
+
+ if (! rectCache.isEmpty())
{
- repaintWorker.setLive(true);
- SwingUtilities.invokeLater(repaintWorker);
+ if (dirtyComponents.containsKey(component))
+ {
+ SwingUtilities.computeUnion(rectCache.x, rectCache.y,
+ rectCache.width, rectCache.height,
+ (Rectangle) dirtyComponents.get(component));
+ }
+ else
+ {
+ synchronized (dirtyComponents)
+ {
+ dirtyComponents.put(component, rectCache.getBounds());
+ }
+ }
+
+ if (! repaintWorker.isLive())
+ {
+ repaintWorker.setLive(true);
+ SwingUtilities.invokeLater(repaintWorker);
+ }
}
}
/**
- * Inserts a component into the repaintOrder list in an ordered fashion,
- * using a binary search.
- *
- * @param c the component to be inserted
- */
- private void insertInRepaintOrder(JComponent c)
- {
- if (comparator == null)
- comparator = new ComponentComparator();
- int insertIndex = Collections.binarySearch(repaintOrder, c, comparator);
- if (insertIndex < 0)
- insertIndex = -(insertIndex + 1);
- repaintOrder.add(insertIndex, c);
- }
-
- /**
* Get the dirty region associated with a component, or <code>null</code>
* if the component has no dirty region.
*
@@ -489,7 +495,7 @@ public class RepaintManager
*/
public void markCompletelyClean(JComponent component)
{
- synchronized (this)
+ synchronized (dirtyComponents)
{
dirtyComponents.remove(component);
}
@@ -523,63 +529,58 @@ public class RepaintManager
*/
public void validateInvalidComponents()
{
- // In order to keep the blocking of application threads minimal, we switch
- // the invalidComponents field with the workInvalidComponents field and
- // work with the workInvalidComponents field.
- synchronized(this)
- {
- ArrayList swap = invalidComponents;
- invalidComponents = workInvalidComponents;
- workInvalidComponents = swap;
- }
- for (Iterator i = workInvalidComponents.iterator(); i.hasNext(); )
+ // We don't use an iterator here because that would fail when there are
+ // components invalidated during the validation of others, which happens
+ // quite frequently. Instead we synchronize the access a little more.
+ while (invalidComponents.size() > 0)
{
- Component comp = (Component) i.next();
- // Find validate root.
- while ((!(comp instanceof JComponent)
- || !((JComponent) comp).isValidateRoot())
- && comp.getParent() != null)
- comp = comp.getParent();
-
- // Validate the validate root.
+ Component comp;
+ synchronized (invalidComponents)
+ {
+ comp = (Component) invalidComponents.remove(0);
+ }
+ // Validate the validate component.
if (! (comp.isVisible() && comp.isShowing()))
continue;
comp.validate();
}
- workInvalidComponents.clear();
}
/**
* Repaint all regions of all components which have been marked dirty in
* the {@link #dirtyComponents} table.
*/
- public synchronized void paintDirtyRegions()
+ public void paintDirtyRegions()
{
- // In order to keep the blocking of application threads minimal, we switch
- // the dirtyComponents field with the workdirtyComponents field and the
- // repaintOrder field with the workRepaintOrder field and work with the
- // work* fields.
- synchronized(this)
- {
- ArrayList swap = workRepaintOrder;
- workRepaintOrder = repaintOrder;
- repaintOrder = swap;
- HashMap swap2 = workDirtyComponents;
- workDirtyComponents = dirtyComponents;
- dirtyComponents = swap2;
- }
- for (Iterator i = workRepaintOrder.iterator(); i.hasNext();)
+ // Short cicuit if there is nothing to paint.
+ if (dirtyComponents.size() == 0)
+ return;
+
+ synchronized (dirtyComponents)
{
- JComponent comp = (JComponent) i.next();
- // If a component is marked completely clean in the meantime, then skip
- // it.
- Rectangle damaged = (Rectangle) workDirtyComponents.get(comp);
- if (damaged == null || damaged.isEmpty())
- continue;
- comp.paintImmediately(damaged);
+ // We sort the components by their size here. This way we have a good
+ // chance that painting the bigger components also paints the smaller
+ // components and we don't need to paint them twice.
+ ArrayList repaintOrder = new ArrayList(dirtyComponents.size());
+ repaintOrder.addAll(dirtyComponents.keySet());
+ if (comparator == null)
+ comparator = new ComponentComparator();
+ Collections.sort(repaintOrder, comparator);
+ repaintUnderway = true;
+ for (Iterator i = repaintOrder.iterator(); i.hasNext();)
+ {
+ JComponent comp = (JComponent) i.next();
+ // If a component is marked completely clean in the meantime, then skip
+ // it.
+ Rectangle damaged = (Rectangle) dirtyComponents.get(comp);
+ if (damaged == null || damaged.isEmpty())
+ continue;
+ comp.paintImmediately(damaged);
+ dirtyComponents.remove(comp);
+ }
+ repaintUnderway = false;
+ commitRemainingBuffers();
}
- workRepaintOrder.clear();
- workDirtyComponents.clear();
}
/**
@@ -592,21 +593,114 @@ public class RepaintManager
* @param proposedHeight The proposed height of the offscreen buffer
*
* @return A shared offscreen buffer for painting
- *
- * @see #doubleBuffer
*/
public Image getOffscreenBuffer(Component component, int proposedWidth,
int proposedHeight)
{
- if (doubleBuffer == null
- || (((doubleBuffer.getWidth(null) < proposedWidth)
- || (doubleBuffer.getHeight(null) < proposedHeight))
- && (proposedWidth < doubleBufferMaximumSize.width)
- && (proposedHeight < doubleBufferMaximumSize.height)))
+ Component root = SwingUtilities.getRoot(component);
+ Image buffer = (Image) offscreenBuffers.get(root);
+ if (buffer == null
+ || buffer.getWidth(null) < proposedWidth
+ || buffer.getHeight(null) < proposedHeight)
+ {
+ int width = Math.max(proposedWidth, root.getWidth());
+ width = Math.min(doubleBufferMaximumSize.width, width);
+ int height = Math.max(proposedHeight, root.getHeight());
+ height = Math.min(doubleBufferMaximumSize.height, height);
+ buffer = component.createImage(width, height);
+ offscreenBuffers.put(root, buffer);
+ }
+ return buffer;
+ }
+
+ /**
+ * Blits the back buffer of the specified root component to the screen. If
+ * the RepaintManager is currently working on a paint request, the commit
+ * requests are queued up and committed at once when the paint request is
+ * done (by {@link #commitRemainingBuffers}). This is package private because
+ * it must get called by JComponent.
+ *
+ * @param root the component, either a Window or an Applet instance
+ * @param area the area to paint on screen
+ */
+ void commitBuffer(Component root, Rectangle area)
+ {
+ // We synchronize on dirtyComponents here because that is what
+ // paintDirtyRegions also synchronizes on while painting.
+ synchronized (dirtyComponents)
+ {
+ // If the RepaintManager is not currently painting, then directly
+ // blit the requested buffer on the screen.
+ if (! repaintUnderway)
+ {
+ Graphics g = root.getGraphics();
+ Image buffer = (Image) offscreenBuffers.get(root);
+ Rectangle clip = g.getClipBounds();
+ if (clip != null)
+ area = SwingUtilities.computeIntersection(clip.x, clip.y,
+ clip.width, clip.height,
+ area);
+ int dx1 = area.x;
+ int dy1 = area.y;
+ int dx2 = area.x + area.width;
+ int dy2 = area.y + area.height;
+ // Make sure we have a sane clip at this point.
+ g.clipRect(area.x, area.y, area.width, area.height);
+
+ // Make sure the coordinates are inside the buffer, everything else
+ // might lead to problems.
+ // TODO: This code should not really be necessary, however, in fact
+ // we have two issues here:
+ // 1. We shouldn't get repaint requests in areas outside the buffer
+ // region in the first place. This still happens for example
+ // when a component is inside a JViewport, and the component has
+ // a size that would reach beyond the window size.
+ // 2. Graphics.drawImage() should not behave strange when trying
+ // to draw regions outside the image.
+ int bufferWidth = buffer.getWidth(root);
+ int bufferHeight = buffer.getHeight(root);
+ dx1 = Math.min(bufferWidth, dx1);
+ dy1 = Math.min(bufferHeight, dy1);
+ dx2 = Math.min(bufferWidth, dx2);
+ dy2 = Math.min(bufferHeight, dy2);
+ g.drawImage(buffer, dx1, dy1, dx2, dy2,
+ dx1, dy1, dx2, dy2, root);
+ g.dispose();
+ }
+ // Otherwise queue this request up, until all the RepaintManager work
+ // is done.
+ else
+ {
+ if (commitRequests.containsKey(root))
+ SwingUtilities.computeUnion(area.x, area.y, area.width,
+ area.height,
+ (Rectangle) commitRequests.get(root));
+ else
+ commitRequests.put(root, area);
+ }
+ }
+ }
+
+ /**
+ * Commits the queued up back buffers to screen all at once.
+ */
+ private void commitRemainingBuffers()
+ {
+ // We synchronize on dirtyComponents here because that is what
+ // paintDirtyRegions also synchronizes on while painting.
+ synchronized (dirtyComponents)
{
- doubleBuffer = component.createImage(proposedWidth, proposedHeight);
+ Set entrySet = commitRequests.entrySet();
+ Iterator i = entrySet.iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ Component root = (Component) entry.getKey();
+ Rectangle area = (Rectangle) entry.getValue();
+ commitBuffer(root, area);
+ i.remove();
+ }
}
- return doubleBuffer;
}
/**