diff options
Diffstat (limited to 'libjava/classpath/javax/swing/RepaintManager.java')
-rw-r--r-- | libjava/classpath/javax/swing/RepaintManager.java | 392 |
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; } /** |