Testing shows that for heavyweight components, all pending paint events get coalesced. The ComponentPeer.coalescePaintEvent() method is used to track the dirty area of a heavyweight component.
I implemented it that way for the GtkComponentPeer and SwingComponentPeer. 2006-11-29 Roman Kennke <[EMAIL PROTECTED]> * java/awt/Component.java (isShowing): Simplified condition code and avoid unnecessary if-codepaths. (coalesceEvents): Always coalesce paint events and let the peer figure out the expanding of the repaint area. * gnu/java/awt/peer/swing/SwingComponentPeer.java (currentPaintEvents): Removed. Replaced by paintArea. (paintArea): New field. Tracks the dirty area. (SwingComponentPeer): Removed init of currentPaintEvents. (coalescePaintEvent): Simplified to only union the dirty regions. (handleEvent): Paint dirty region that was tracked in paintArea. * gnu/java/awt/peer/gtk/GtkComponentPeer.java (paintArea): New field. Tracks the dirty region. (coalescePaintEvent): Implemented to track the dirty region. (paintComponent): Use the dirty region in paintArea. Protect state by putting the paint and dispose code in a try-finally. (updateComponent): Use the dirty region in paintArea. Protect state by putting the paint and dispose code in a try-finally. /Roman
Index: java/awt/Component.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/awt/Component.java,v retrieving revision 1.150 diff -u -1 -5 -r1.150 Component.java --- java/awt/Component.java 13 Oct 2006 15:15:12 -0000 1.150 +++ java/awt/Component.java 29 Nov 2006 12:56:04 -0000 @@ -811,34 +811,31 @@ public boolean isVisible() { return visible; } /** * Tests whether or not this component is actually being shown on * the screen. This will be true if and only if it this component is * visible and its parent components are all visible. * * @return true if the component is showing on the screen * @see #setVisible(boolean) */ public boolean isShowing() { - if (! visible || peer == null) - return false; - - return parent == null ? false : parent.isShowing(); + return visible && peer != null && (parent == null || parent.isShowing()); } /** * Tests whether or not this component is enabled. Components are enabled * by default, and must be enabled to receive user input or generate events. * * @return true if the component is enabled * @see #setEnabled(boolean) */ public boolean isEnabled() { return enabled; } /** @@ -3615,30 +3612,36 @@ break; case PaintEvent.PAINT: case PaintEvent.UPDATE: // For heavyweights the EventQueue should ask the peer. if (peer == null || peer instanceof LightweightPeer) { PaintEvent pe1 = (PaintEvent) existingEvent; PaintEvent pe2 = (PaintEvent) newEvent; Rectangle r1 = pe1.getUpdateRect(); Rectangle r2 = pe2.getUpdateRect(); if (r1.contains(r2)) coalesced = existingEvent; else if (r2.contains(r1)) coalesced = newEvent; } + else + { + // Replace the event and let the heavyweight figure out the expanding + // of the repaint area. + coalesced = newEvent; + } break; default: coalesced = null; } return coalesced; } /** * Processes the specified event. In this class, this method simply * calls one of the more specific event handlers. * * @param e the event to process * @throws NullPointerException if e is null * @see #processComponentEvent(ComponentEvent) * @see #processFocusEvent(FocusEvent) Index: gnu/java/awt/peer/swing/SwingComponentPeer.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/swing/SwingComponentPeer.java,v retrieving revision 1.6 diff -u -1 -5 -r1.6 SwingComponentPeer.java --- gnu/java/awt/peer/swing/SwingComponentPeer.java 9 Nov 2006 20:53:24 -0000 1.6 +++ gnu/java/awt/peer/swing/SwingComponentPeer.java 29 Nov 2006 12:56:04 -0000 @@ -52,95 +52,85 @@ import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.BufferCapabilities.FlipContents; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.PaintEvent; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.ImageProducer; import java.awt.image.VolatileImage; import java.awt.peer.ComponentPeer; import java.awt.peer.ContainerPeer; import java.awt.peer.LightweightPeer; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; import javax.swing.JComponent; import javax.swing.RepaintManager; /** * The base class for Swing based component peers. This provides the basic * functionality needed for Swing based component peers. Many methods are * implemented to forward to the Swing component. Others however forward * to the component's parent and expect the toplevel component peer to provide * a real implementation of it. These are for example the key methods * [EMAIL PROTECTED] #getGraphics()} and [EMAIL PROTECTED] #createImage(int, int)}, as well as * [EMAIL PROTECTED] #getLocationOnScreen()}. * * This class also provides the necesary hooks into the Swing painting and * event handling system. In order to achieve this, it traps paint, mouse and * key events in [EMAIL PROTECTED] #handleEvent(AWTEvent)} and calls some special methods - * ([EMAIL PROTECTED] #peerPaint(Graphics,boolean)}, [EMAIL PROTECTED] #handleKeyEvent(KeyEvent)}, + * ([EMAIL PROTECTED] #peerPaint(Graphics)}, [EMAIL PROTECTED] #handleKeyEvent(KeyEvent)}, * [EMAIL PROTECTED] #handleMouseEvent(MouseEvent)} and * [EMAIL PROTECTED] #handleMouseMotionEvent(MouseEvent)}) that call the corresponding * Swing methods. * * @author Roman Kennke ([EMAIL PROTECTED]) */ public class SwingComponentPeer implements ComponentPeer { /** * The AWT component for this peer. */ protected Component awtComponent; /** * The Swing component for this peer. */ protected SwingComponent swingComponent; /** * The font that is set for this peer. */ protected Font peerFont; /** - * The repaint requests that will be handled next. The events queued - * up here are in the exact same order as they appear in - * [EMAIL PROTECTED] #coalescePaintEvent(PaintEvent)}, that is in event queue order. - * This is used for coalescing paint events. - * - * @see #coalescePaintEvent(PaintEvent) + * The current repaint area. */ - protected List currentPaintEvents; + protected Rectangle paintArea; /** * Creates a SwingComponentPeer instance. Subclasses are expected to call - * this constructor and thereafter call [EMAIL PROTECTED] #init(Component, - * SwingComponent)}in order to setup the AWT and Swing components properly. + * this constructor and thereafter call [EMAIL PROTECTED] #init(Component, JComponent)} + * in order to setup the AWT and Swing components properly. */ protected SwingComponentPeer() { - // Initialize paint event queue. - currentPaintEvents = new LinkedList(); - + // Nothing to do here. } /** * Initializes the AWT and Swing component for this peer. It is expected that * subclasses call this from within their constructor. * * @param awtComp the AWT component for this peer * @param swingComp the Swing component for this peer */ protected void init(Component awtComp, SwingComponent swingComp) { awtComponent = awtComp; swingComponent = swingComp; if (swingComponent != null) { @@ -400,48 +390,46 @@ * [EMAIL PROTECTED] Component#dispatchEvent(AWTEvent)} to give the peer a chance to * react to events for the component. * * @param e the event */ public void handleEvent(AWTEvent e) { switch (e.getID()) { case PaintEvent.UPDATE: case PaintEvent.PAINT: // Need to synchronize to avoid threading problems on the // paint event list. // We must synchronize on the tree lock first to avoid deadlock, // because Container.paint() will grab it anyway. - synchronized (awtComponent.getTreeLock()) + synchronized (this) { - synchronized (currentPaintEvents) + assert paintArea != null; + if (awtComponent.isShowing()) { - if (currentPaintEvents.contains(e)) + Graphics g = awtComponent.getGraphics(); + try { - Graphics g = awtComponent.getGraphics(); - try - { - Rectangle clip = ((PaintEvent) e).getUpdateRect(); - g.clipRect(clip.x, clip.y, clip.width, clip.height); - peerPaint(g, e.getID() == PaintEvent.UPDATE); - } - finally - { - g.dispose(); - } - currentPaintEvents.remove(e); + Rectangle clip = paintArea; + g.clipRect(clip.x, clip.y, clip.width, clip.height); + peerPaint(g, e.getID() == PaintEvent.UPDATE); + } + finally + { + g.dispose(); + paintArea = null; } } } break; case MouseEvent.MOUSE_PRESSED: case MouseEvent.MOUSE_RELEASED: case MouseEvent.MOUSE_CLICKED: case MouseEvent.MOUSE_ENTERED: case MouseEvent.MOUSE_EXITED: handleMouseEvent((MouseEvent) e); break; case MouseEvent.MOUSE_MOVED: case MouseEvent.MOUSE_DRAGGED: handleMouseMotionEvent((MouseEvent) e); break; @@ -820,59 +808,37 @@ * @return <code>true</code> if this component peer can determine if the * component has been obscured, <code>false</code> otherwise */ public boolean canDetermineObscurity() { return false; } /** * Coalesces the specified paint event. * * @param e the paint event */ public void coalescePaintEvent(PaintEvent e) { - synchronized (currentPaintEvents) + synchronized (this) { Rectangle newRect = e.getUpdateRect(); - boolean coalesced = false; - for (Iterator i = currentPaintEvents.iterator(); i.hasNext() && ! coalesced;) - { - PaintEvent e2 = (PaintEvent) i.next(); - if (e.getID() == e2.getID()) - { - Rectangle oldRect = e2.getUpdateRect(); - if (oldRect.contains(newRect)) - { - // Merge newRect into oldRect. We have to discard the old request - // so that the events are still in the correct order. - i.remove(); - newRect.setBounds(oldRect); - coalesced = true; - } - else if (newRect.contains(oldRect)) - { - // Merge oldRect into newRect. We have to discard the old request - // so that the events are still in the correct order. - i.remove(); - coalesced = true; - } - } - // TODO: Maybe do something more clever here. - } - currentPaintEvents.add(e); + if (paintArea == null) + paintArea = newRect; + else + Rectangle.union(paintArea, newRect, paintArea); } } /** * Updates the cursor. This is not yet implemented. */ public void updateCursorImmediately() { // Nothing to do here yet. } /** * Returns true, if this component can handle wheel scrolling, * <code>false</code> otherwise. * Index: gnu/java/awt/peer/gtk/GtkComponentPeer.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/GtkComponentPeer.java,v retrieving revision 1.121 diff -u -1 -5 -r1.121 GtkComponentPeer.java --- gnu/java/awt/peer/gtk/GtkComponentPeer.java 8 Aug 2006 22:23:36 -0000 1.121 +++ gnu/java/awt/peer/gtk/GtkComponentPeer.java 29 Nov 2006 12:56:04 -0000 @@ -78,30 +78,35 @@ import java.awt.peer.LightweightPeer; import java.awt.peer.WindowPeer; import java.util.Timer; import java.util.TimerTask; public class GtkComponentPeer extends GtkGenericPeer implements ComponentPeer { VolatileImage backBuffer; BufferCapabilities caps; Component awtComponent; Insets insets; + /** + * The current repaint area. + */ + protected Rectangle paintArea; + /* this isEnabled differs from Component.isEnabled, in that it knows if a parent is disabled. In that case Component.isEnabled may return true, but our isEnabled will always return false */ native boolean isEnabled (); static native boolean modalHasGrab(); native int[] gtkWidgetGetForeground (); native int[] gtkWidgetGetBackground (); native void gtkWidgetGetDimensions (int[] dim); native void gtkWidgetGetPreferredDimensions (int[] dim); native void gtkWindowGetLocationOnScreen (int[] point); native void gtkWidgetGetLocationOnScreen (int[] point); native void gtkWidgetSetCursor (int type, GtkImage image, int x, int y); native void gtkWidgetSetCursorUnlocked (int type, GtkImage image, int x, int y); @@ -296,56 +301,70 @@ // This method and its overrides are the only methods in the peers // that should call awtComponent.paint. protected void paintComponent (PaintEvent event) { // Do not call Component.paint if the component is not showing or // if its bounds form a degenerate rectangle. if (!awtComponent.isShowing() || (awtComponent.getWidth() < 1 || awtComponent.getHeight() < 1)) return; // Creating and disposing a GdkGraphics every time paint is called // seems expensive. However, the graphics state does not carry // over between calls to paint, and resetting the graphics object // may even be more costly than simply creating a new one. - Graphics g = getGraphics(); - - g.setClip(event.getUpdateRect()); - - awtComponent.paint(g); - - g.dispose(); + synchronized (paintArea) + { + Graphics g = getGraphics(); + try + { + g.setClip(paintArea); + awtComponent.paint(g); + } + finally + { + g.dispose(); + paintArea = null; + } + } } // This method and its overrides are the only methods in the peers // that should call awtComponent.update. protected void updateComponent (PaintEvent event) { // Do not call Component.update if the component is not showing or // if its bounds form a degenerate rectangle. if (!awtComponent.isShowing() || (awtComponent.getWidth() < 1 || awtComponent.getHeight() < 1)) return; - Graphics g = getGraphics(); - - g.setClip(event.getUpdateRect()); - - awtComponent.update(g); - - g.dispose(); + synchronized (paintArea) + { + Graphics g = getGraphics(); + try + { + g.setClip(paintArea); + awtComponent.update(g); + } + finally + { + g.dispose(); + paintArea = null; + } + } } public boolean isFocusTraversable () { return true; } public Dimension minimumSize () { int dim[] = new int[2]; gtkWidgetGetPreferredDimensions (dim); return new Dimension (dim[0], dim[1]); } @@ -742,31 +761,38 @@ return comp == awtComponent; } public boolean isObscured () { return false; } public boolean canDetermineObscurity () { return false; } public void coalescePaintEvent (PaintEvent e) { - + synchronized (this) + { + Rectangle newRect = e.getUpdateRect(); + if (paintArea == null) + paintArea = newRect; + else + Rectangle.union(paintArea, newRect, paintArea); + } } public void updateCursorImmediately () { if (awtComponent.getCursor() != null) setCursor(awtComponent.getCursor()); } public boolean handlesWheelScrolling () { return false; } // Convenience method to create a new volatile image on the screen // on which this component is displayed.