Index: javax/swing/JComponent.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/JComponent.java,v retrieving revision 1.68 diff -u -r1.68 JComponent.java --- javax/swing/JComponent.java 12 Oct 2005 12:41:27 -0000 1.68 +++ javax/swing/JComponent.java 14 Oct 2005 21:57:46 -0000 @@ -258,7 +258,6 @@ * * @see #setDoubleBuffered * @see #isDoubleBuffered - * @see #paintLock * @see #paint */ boolean doubleBuffered = true; @@ -387,17 +386,6 @@ */ private static transient Rectangle rectCache; - /** - * A lock held during recursive painting; this is used to serialize - * access to the double buffer, and also to select the "top level" - * object which should acquire the double buffer in a given widget - * tree (which may have multiple double buffered children). - * - * @see #doubleBuffered - * @see #paint - */ - private static final Object paintLock = new Object(); - /** * The default locale of the component. * @@ -441,6 +429,13 @@ public static final int WHEN_IN_FOCUSED_WINDOW = 2; /** + * Indicates if this component is completely dirty or not. This is used + * by the RepaintManager's + * address@hidden RepaintManager#isCompletelyDirty(JComponent)} method. + */ + boolean isCompletelyDirty = false; + + /** * Creates a new JComponent instance. */ public JComponent() @@ -874,9 +869,13 @@ */ public void setBorder(Border newBorder) { - Border oldBorder = border; + Border oldBorder = getBorder(); + if (oldBorder == newBorder) + return; + border = newBorder; firePropertyChange("border", oldBorder, newBorder); + repaint(); } /** @@ -1482,6 +1481,10 @@ paintComponent(g); paintBorder(g); paintChildren(g); + Rectangle clip = g.getClipBounds(); + if (clip.x == 0 && clip.y == 0 && clip.width == getWidth() + && clip.height == getHeight()) + RepaintManager.currentManager(this).markCompletelyClean(this); } } @@ -1616,11 +1619,12 @@ public void paintImmediately(Rectangle r) { // Try to find a root pane for this component. - Component root = SwingUtilities.getRootPane(this); - // If no root pane can be found, then try to find the Window that contains - // this component. + //Component root = findPaintRoot(r); + Component root = findPaintRoot(r); + // If no paint root is found, then this component is completely overlapped + // by another component and we don't need repainting. if (root == null) - root = SwingUtilities.getRoot(this); + return; if (root == null || !root.isShowing()) return; @@ -1662,20 +1666,17 @@ RepaintManager rm = RepaintManager.currentManager(this); // Paint on the offscreen buffer. - synchronized (paintLock) - { - Image buffer = rm.getOffscreenBuffer(this, getWidth(), getHeight()); - Graphics g2 = buffer.getGraphics(); - g2 = getComponentGraphics(g2); - g2.setClip(r.x, r.y, r.width, r.height); - isPaintingDoubleBuffered = true; - paint(g2); - isPaintingDoubleBuffered = false; - g2.dispose(); - - // Paint the buffer contents on screen. - g.drawImage(buffer, 0, 0, this); - } + Image buffer = rm.getOffscreenBuffer(this, getWidth(), getHeight()); + Graphics g2 = buffer.getGraphics(); + g2 = getComponentGraphics(g2); + g2.setClip(r.x, r.y, r.width, r.height); + isPaintingDoubleBuffered = true; + paint(g2); + isPaintingDoubleBuffered = false; + g2.dispose(); + + // Paint the buffer contents on screen. + g.drawImage(buffer, 0, 0, this); } /** @@ -2188,9 +2189,11 @@ */ public void setEnabled(boolean enable) { - boolean oldEnabled = isEnabled(); + if (enable == isEnabled()) + return; super.setEnabled(enable); - firePropertyChange("enabled", oldEnabled, enable); + firePropertyChange("enabled", !enable, enable); + repaint(); } /** @@ -2200,14 +2203,11 @@ */ public void setFont(Font f) { - if (f == null && getFont() == null) + if (f == getFont()) return; - - if (f == null || !f.equals(getFont())) - { - super.setFont(f); - revalidate(); - } + super.setFont(f); + revalidate(); + repaint(); } /** @@ -2217,7 +2217,10 @@ */ public void setBackground(Color bg) { + if (bg == getBackground()) + return; super.setBackground(bg); + repaint(); } /** @@ -2227,7 +2230,10 @@ */ public void setForeground(Color fg) { + if (fg == getForeground()) + return; super.setForeground(fg); + repaint(); } /** @@ -2403,6 +2409,7 @@ firePropertyChange("UI", oldUI, newUI); revalidate(); + repaint(); } /** @@ -2935,5 +2942,136 @@ JComponent jc = (JComponent) children[i]; jc.fireAncestorEvent(ancestor, id); } + } + + /** + * Finds a suitable paint root for painting this component. This method first + * checks if this component is overlapped using + * address@hidden #findOverlapFreeParent(Rectangle)}. The returned paint root is then + * feeded to address@hidden #findOpaqueParent(Component)} to find the nearest opaque + * component for this paint root. If no paint is necessary, then we return + * null. + * + * @param c the clip of this component + * + * @return the paint root or null if no painting is necessary + */ + private Component findPaintRoot(Rectangle c) + { + Component p = findOverlapFreeParent(c); + if (p == null) + return null; + Component root = findOpaqueParent(p); + return root; + } + + /** + * Scans the containment hierarchy upwards for components that overlap the + * this component in the specified clip. This method returns + * this, if no component overlaps this component. It returns + * null if another component completely covers this component + * in the specified clip (no repaint necessary). If another component partly + * overlaps this component in the specified clip, then the parent of this + * component is returned (this is the component that must be used as repaint + * root). For efficient lookup, the method + * address@hidden #isOptimizedDrawingEnabled()} is used. + * + * @param clip the clip of this component + * + * @return the paint root, or null if no paint is necessary + */ + private Component findOverlapFreeParent(Rectangle clip) + { + Rectangle currentClip = clip; + Component found = this; + Container parent = this; + while (parent != null) + { + Container newParent = parent.getParent(); + if (newParent == null) + break; + // If the parent is optimizedDrawingEnabled, then its children are + // tiled and cannot have an overlapping child. Go directly to next + // parent. + if (newParent instanceof JComponent + && ((JComponent) newParent).isOptimizedDrawingEnabled()) + { + parent = newParent; + continue; + } + + // First we must check if the new parent itself somehow clips the + // target rectangle. This can happen in JViewports. + Rectangle parRect = new Rectangle(0, 0, newParent.getWidth(), + newParent.getHeight()); + Rectangle target = SwingUtilities.convertRectangle(found, + currentClip, + newParent); + if (target.contains(parRect) || target.intersects(parRect)) + { + found = newParent; + currentClip = target; + parent = newParent; + continue; + } + + // Otherwise we must check if one of the children of this parent + // overlaps with the current component. + Component[] children = newParent.getComponents(); + // This flag is used to skip components that are 'below' the component + // in question. + boolean skip = true; + for (int i = children.length - 1; i >= 0; i--) + { + if (children[i] == parent) + skip = false; + if (skip) + continue; + Component c = children[i]; + Rectangle compBounds = c.getBounds(); + // If the component completely overlaps the clip in question, we + // don't need to repaint. Return null. + if (compBounds.contains(target)) + return null; + if (compBounds.intersects(target)) + { + // We found a parent whose children overlap with our current + // component. Make this the current component. + found = newParent; + currentClip = target; + break; + } + } + parent = newParent; + } + return found; + } + + /** + * Finds the nearest component to c (upwards in the containment + * hierarchy), that is opaque. If c itself is opaque, + * this returns c itself. + * + * @param c the start component for the search + * @return the nearest component to c (upwards in the containment + * hierarchy), that is opaque; If c itself is opaque, + * this returns c itself + */ + private Component findOpaqueParent(Component c) + { + Component found = c; + while (true) + { + if ((found instanceof JComponent) && ((JComponent) found).isOpaque()) + break; + else if (!(found instanceof JComponent)) + break; + Container p = found.getParent(); + if (p == null) + break; + else + found = p; + } + return found; } } Index: javax/swing/RepaintManager.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/RepaintManager.java,v retrieving revision 1.14 diff -u -r1.14 RepaintManager.java --- javax/swing/RepaintManager.java 13 Sep 2005 09:17:21 -0000 1.14 +++ javax/swing/RepaintManager.java 14 Oct 2005 21:57:48 -0000 @@ -43,6 +43,9 @@ import java.awt.Image; import java.awt.Rectangle; import java.awt.image.VolatileImage; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; @@ -111,6 +114,62 @@ } + /** + * Compares two components using their depths in the component hierarchy. + * A component with a lesser depth (higher level components) are sorted + * before components with a deeper depth (low level components). This is used + * to order paint requests, so that the higher level components are painted + * before the low level components get painted. + * + * @author Roman Kennke (address@hidden) + */ + private class ComponentComparator implements Comparator + { + + /** + * Compares two components. + * + * @param o1 the first component + * @param o2 the second component + * + * @return a negative integer, if o1 is higher in the + * hierarchy than o2, zero, if both are at the same + * level and a positive integer, if o1 is deeper in + * the hierarchy than o2 + */ + public int compare(Object o1, Object o2) + { + if (o1 instanceof JComponent && o2 instanceof JComponent) + { + JComponent c1 = (JComponent) o1; + 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++; + } + return depth; + } + } + /** * A table storing the dirty regions of components. The keys of this * table are components, the values are rectangles. Each component maps @@ -123,7 +182,20 @@ * @see #markCompletelyClean * @see #markCompletelyDirty */ - Hashtable dirtyComponents; + 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; /** * A single, shared instance of the helper class. Any methods which mark @@ -146,7 +218,8 @@ * @see #removeInvalidComponent * @see #validateInvalidComponents */ - Vector invalidComponents; + ArrayList invalidComponents; + ArrayList workInvalidComponents; /** * Whether or not double buffering is enabled on this repaint @@ -194,8 +267,12 @@ */ public RepaintManager() { - dirtyComponents = new Hashtable(); - invalidComponents = new Vector(); + 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; @@ -291,7 +368,7 @@ */ public synchronized void removeInvalidComponent(JComponent component) { - invalidComponents.removeElement(component); + invalidComponents.remove(component); } /** @@ -315,12 +392,13 @@ public synchronized void addDirtyRegion(JComponent component, int x, int y, int w, int h) { - if (w == 0 || h == 0) + 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()) { @@ -328,7 +406,23 @@ 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 null * if the component has no dirty region. @@ -345,7 +439,10 @@ */ public Rectangle getDirtyRegion(JComponent component) { - return (Rectangle) dirtyComponents.get(component); + Rectangle dirty = (Rectangle) dirtyComponents.get(component); + if (dirty == null) + dirty = new Rectangle(); + return dirty; } /** @@ -363,6 +460,7 @@ { Rectangle r = component.getBounds(); addDirtyRegion(component, r.x, r.y, r.width, r.height); + component.isCompletelyDirty = true; } /** @@ -378,7 +476,11 @@ */ public void markCompletelyClean(JComponent component) { - dirtyComponents.remove(component); + synchronized (this) + { + dirtyComponents.remove(component); + } + component.isCompletelyDirty = false; } /** @@ -397,13 +499,9 @@ */ public boolean isCompletelyDirty(JComponent component) { - Rectangle dirty = (Rectangle) dirtyComponents.get(component); - if (dirty == null) + if (! dirtyComponents.containsKey(component)) return false; - Rectangle r = component.getBounds(); - if (r == null) - return true; - return dirty.contains(r); + return component.isCompletelyDirty; } /** @@ -412,58 +510,56 @@ */ public void validateInvalidComponents() { - for (Enumeration e = invalidComponents.elements(); e.hasMoreElements(); ) + // In order to keep the blocking of application threads minimal, we switch + // the invalidComponents field with the workInvalidComponents field and + // work wíth the workInvalidComponents field. + synchronized(this) + { + ArrayList swap = invalidComponents; + invalidComponents = workInvalidComponents; + workInvalidComponents = swap; + } + for (Iterator i = workInvalidComponents.iterator(); i.hasNext(); ) { - JComponent comp = (JComponent) e.nextElement(); + JComponent comp = (JComponent) i.next(); if (! (comp.isVisible() && comp.isShowing())) continue; comp.validate(); } - invalidComponents.clear(); + workInvalidComponents.clear(); } /** * Repaint all regions of all components which have been marked dirty in * the address@hidden #dirtyComponents} table. */ - public void paintDirtyRegions() + public synchronized void paintDirtyRegions() { - // step 1: pull out roots and calculate spanning damage - - HashMap roots = new HashMap(); - for (Enumeration e = dirtyComponents.keys(); e.hasMoreElements(); ) + // 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();) { - JComponent comp = (JComponent) e.nextElement(); - if (! (comp.isVisible() && comp.isShowing())) + 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; - Rectangle damaged = getDirtyRegion(comp); - if (damaged.width == 0 || damaged.height == 0) - continue; - JRootPane root = comp.getRootPane(); - // If the component has no root, no repainting will occur. - if (root == null) - continue; - Rectangle rootDamage = SwingUtilities.convertRectangle(comp, damaged, root); - if (! roots.containsKey(root)) - { - roots.put(root, rootDamage); - } - else - { - roots.put(root, ((Rectangle)roots.get(root)).union(rootDamage)); - } - } - dirtyComponents.clear(); - - // step 2: paint those roots - Iterator i = roots.entrySet().iterator(); - while(i.hasNext()) - { - Map.Entry ent = (Map.Entry) i.next(); - JRootPane root = (JRootPane) ent.getKey(); - Rectangle rect = (Rectangle) ent.getValue(); - root.paintImmediately(rect); + comp.paintImmediately(damaged); } + workRepaintOrder.clear(); + workDirtyComponents.clear(); } /**