I have been working a little more with Espial/Espresso and some dummy peers against the JDK, which is quite interesting and helps alot to get our AWT core straight. Here's what I found and fixed:

- reshape() only needs to invalidate the component itself when it has actually been resized. For moved-only components we only need to invalidate the parent.
- Repainting in reshape() is now slightly more accurate.
- The paint() and update() methods have been cleared up. I did some more tests against the JDK and hope it's alrighth now. Unfortunately I can't think of an easy way to check this in Mauve (I basically examined stacktraces that I dumped in plain Component and Container subclasses). - repaint() doesn't call ComponentPeer.repaint() but instead does send an UPDATE event itself. Also, it only sends events to heavyweight components (I don't like the concept of a lightweight peer anyway) and translates the rectangle accordingly. This should take a little burden away from the peers, since this is peer-independent anyway.

I couldn't observe any regressions with that patch, so I think it's good to have it in. Espial/Espresso looks quite good now :-) Unfortunately it isn't free software, but still a nice block of AWT code to test Classpath against.

2006-07-27  Roman Kennke  <[EMAIL PROTECTED]>

        * java/awt/Component.java
        (reshape): Invalidate the component itself only when the
        size has changed. Invalidate the parent always. Fixed
        repainting. Pulled out the notification into
        notifyReshape().
        (notifyReshape): New helper method. Notify interested listeners
        about a reshape.
        (update): Simply call paint() without clearing the background.
        This is done in Container.update() if appropriate.
        (repaint): Delagate the repaint to the nearest heavyweight
        parent (for lightweights) and send an UPDATE event, rather than
        calling ComponentPeer.repaint().
        * java/awt/Container.java
        (backCleared): Removed field.
        (paint): Removed handling of backCleared flag.
        (update): Only paint if the container is actually
        showing. Removed handling of backCleared flag.

/Roman
Index: java/awt/Component.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/Component.java,v
retrieving revision 1.138
diff -u -1 -2 -r1.138 Component.java
--- java/awt/Component.java	26 Jul 2006 19:09:50 -0000	1.138
+++ java/awt/Component.java	27 Jul 2006 14:28:46 -0000
@@ -1488,97 +1488,104 @@
    */
   public void reshape(int x, int y, int width, int height)
   {
     // We need to lock the tree here, otherwise we risk races and
     // inconsistencies.
     synchronized (getTreeLock())
       {
         int oldx = this.x;
         int oldy = this.y;
         int oldwidth = this.width;
         int oldheight = this.height;
     
-        if (this.x == x && this.y == y && this.width == width
-            && this.height == height)
-          return;
-
-        invalidate();
-    
-        this.x = x;
-        this.y = y;
-        this.width = width;
-        this.height = height;
-        if (peer != null)
-          peer.setBounds (x, y, width, height);
-    
-        // Erase old bounds and repaint new bounds for lightweights.
-        if (isLightweight() && isShowing())
-          {
-            if (parent != null)
-              {
-                Rectangle oldBounds = new Rectangle(oldx, oldy, oldwidth,
-                                                    oldheight);
-                Rectangle newBounds = new Rectangle(x, y, width, height);
-                Rectangle destroyed = oldBounds.union(newBounds);
-                if (!destroyed.isEmpty())
-                  parent.repaint(0, destroyed.x, destroyed.y, destroyed.width,
-                                 destroyed.height);
-              }
-          }
-
         boolean resized = oldwidth != width || oldheight != height;
         boolean moved = oldx != x || oldy != y;
-        // Only post an event if this component actually has a listener
-        // or has this event explicitly enabled.
-        if (componentListener != null
-            || (eventMask & AWTEvent.COMPONENT_EVENT_MASK) != 0)
+
+        if (resized || moved)
           {
-            // Fire component event on this component.
-            if (moved)
+            // Update the fields.
+            this.x = x;
+            this.y = y;
+            this.width = width;
+            this.height = height;
+
+            if (peer != null)
               {
-                ComponentEvent ce = new ComponentEvent(this,
-                                               ComponentEvent.COMPONENT_MOVED);
-                getToolkit().getSystemEventQueue().postEvent(ce);
+                peer.setBounds (x, y, width, height);
+                if (resized)
+                  invalidate();
+                if (parent != null && parent.valid)
+                  parent.invalidate();
               }
-            if (resized)
+
+            // Send some events to interested listeners.
+            notifyReshape(resized, moved);
+
+            // Repaint this component and the parent if appropriate.
+            if (parent != null && peer instanceof LightweightPeer
+                && isShowing())
               {
-                ComponentEvent ce = new ComponentEvent(this,
-                                             ComponentEvent.COMPONENT_RESIZED);
-                getToolkit().getSystemEventQueue().postEvent(ce);
+                // The parent repaints the area that we occupied before.
+                parent.repaint(oldx, oldy, oldwidth, oldheight);
+                // This component repaints the area that we occupy now.
+                repaint();
               }
           }
-        else
+      }
+  }
+
+  private void notifyReshape(boolean resized, boolean moved)
+  {
+    // Only post an event if this component actually has a listener
+    // or has this event explicitly enabled.
+    if (componentListener != null
+        || (eventMask & AWTEvent.COMPONENT_EVENT_MASK) != 0)
+      {
+        // Fire component event on this component.
+        if (moved)
           {
-            // Otherwise we might need to notify child components when this is
-            // a Container.
-            if (this instanceof Container)
+            ComponentEvent ce = new ComponentEvent(this,
+                                           ComponentEvent.COMPONENT_MOVED);
+            getToolkit().getSystemEventQueue().postEvent(ce);
+          }
+        if (resized)
+          {
+            ComponentEvent ce = new ComponentEvent(this,
+                                         ComponentEvent.COMPONENT_RESIZED);
+            getToolkit().getSystemEventQueue().postEvent(ce);
+          }
+      }
+    else
+      {
+        // Otherwise we might need to notify child components when this is
+        // a Container.
+        if (this instanceof Container)
+          {
+            Container cont = (Container) this;
+            if (resized)
               {
-                Container cont = (Container) this;
-                if (resized)
+                for (int i = 0; i < cont.getComponentCount(); i++)
                   {
-                    for (int i = 0; i < cont.getComponentCount(); i++)
-                      {
-                        Component child = cont.getComponent(i);
-                        child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_RESIZED,
-                                                 this, parent, 0);
-                      }
+                    Component child = cont.getComponent(i);
+                    child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_RESIZED,
+                                             this, parent, 0);
                   }
-                if (moved)
+              }
+            if (moved)
+              {
+                for (int i = 0; i < cont.getComponentCount(); i++)
                   {
-                    for (int i = 0; i < cont.getComponentCount(); i++)
-                      {
-                        Component child = cont.getComponent(i);
-                        child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_MOVED,
-                                                 this, parent, 0);
-                      }
+                    Component child = cont.getComponent(i);
+                    child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_MOVED,
+                                             this, parent, 0);
                   }
               }
           }
       }
   }
 
   /**
    * Sets the bounding rectangle for this component to the specified
    * rectangle. Note that these coordinates are relative to the parent, not
    * to the screen.
    *
    * @param r the new bounding rectangle
@@ -2141,57 +2148,45 @@
    * are not painted.
    *
    * @param g the graphics context for this paint job
    * @see #update(Graphics)
    */
   public void paint(Graphics g)
   {
     // This is a callback method and is meant to be overridden by subclasses
     // that want to perform custom painting.
   }
 
   /**
-   * Updates this component. This is called in response to
-   * <code>repaint</code>. This method fills the component with the
-   * background color, then sets the foreground color of the specified
-   * graphics context to the foreground color of this component and calls
-   * the <code>paint()</code> method. The coordinates of the graphics are
+   * Updates this component. This is called for heavyweight components in
+   * response to [EMAIL PROTECTED] #repaint()}. The default implementation simply forwards
+   * to [EMAIL PROTECTED] #paint(Graphics)}. The coordinates of the graphics are
    * relative to this component. Subclasses should call either
    * <code>super.update(g)</code> or <code>paint(g)</code>.
    *
    * @param g the graphics context for this update
    *
    * @see #paint(Graphics)
    * @see #repaint()
-   *
-   * @specnote In contrast to what the spec says, tests show that the exact
-   *           behaviour is to clear the background on lightweight and
-   *           top-level components only. Heavyweight components are not
-   *           affected by this method and only call paint().
    */
   public void update(Graphics g)
   {
-    // Tests show that the clearing of the background is only done in
-    // two cases:
-    // - If the component is lightweight (yes this is in contrast to the spec).
-    // or
-    // - If the component is a toplevel container.
-    if (isLightweight() || getParent() == null)
-      {
-        Rectangle clip = g.getClipBounds();
-        if (clip == null)
-          g.clearRect(0, 0, width, height);
-        else
-          g.clearRect(clip.x, clip.y, clip.width, clip.height);
-      }
+    // Note 1: We used to clear the background here for lightweights and
+    // toplevel components. Tests show that this is not what the JDK does
+    // here. Note that there is some special handling and background
+    // clearing code in Container.update(Graphics).
+
+    // Note 2 (for peer implementors): The JDK doesn't seem call update() for
+    // toplevel components, even when an UPDATE event is sent (as a result
+    // of repaint).
     paint(g);
   }
 
   /**
    * Paints this entire component, including any sub-components.
    *
    * @param g the graphics context for this paint job
    * 
    * @see #paint(Graphics)
    */
   public void paintAll(Graphics g)
   {
@@ -2249,29 +2244,64 @@
    * approximately the specified number of milliseconds. The coordinates
    * are relative to this component.
    *
    * @param tm milliseconds before this component should be repainted
    * @param x the X coordinate of the upper left of the region to repaint
    * @param y the Y coordinate of the upper left of the region to repaint
    * @param width the width of the region to repaint
    * @param height the height of the region to repaint
    * @see #update(Graphics)
    */
   public void repaint(long tm, int x, int y, int width, int height)
   {
-    if (isShowing())
-      {
-        ComponentPeer p = peer;
-        if (p != null)
-          p.repaint(tm, x, y, width, height);
+    // The repaint() call has previously been delegated to
+    // [EMAIL PROTECTED] ComponentPeer.repaint()}. Testing on the JDK using some
+    // dummy peers show that this methods is never called. I think it makes
+    // sense to actually perform the tasks below here, since it's pretty
+    // much peer independent anyway, and makes sure only heavyweights are
+    // bothered by this.
+    ComponentPeer p = peer;
+
+    // Let the nearest heavyweight parent handle repainting for lightweight
+    // components.
+    // This goes up the hierarchy until we hit
+    // a heavyweight component that handles this and translates the
+    // rectangle while doing so.
+
+    // We perform some boundary checking to restrict the paint
+    // region to this component.
+    int px = (x < 0 ? 0 : x);
+    int py = (y < 0 ? 0 : y);
+    int pw = width;
+    int ph = height;
+    Component par = this;
+    while (par != null && p instanceof LightweightPeer)
+      {
+        px += par.x; 
+        py += par.y; 
+        // We perform some boundary checking to restrict the paint
+        // region to this component.
+        pw = Math.min(pw, par.width);
+        ph = Math.min(ph, par.height);
+        par = par.parent;
+        p = par.peer;
+      }
+
+    // Now send an UPDATE event to the heavyweight component that we've found.
+    if (par != null && par.isVisible() && p != null && pw > 0 && ph > 0)
+      {
+        assert ! (p instanceof LightweightPeer);
+        PaintEvent pe = new PaintEvent(par, PaintEvent.UPDATE,
+                                       new Rectangle(px, py, pw, ph));
+        getToolkit().getSystemEventQueue().postEvent(pe);
       }
   }
 
   /**
    * Prints this component. This method is provided so that printing can be
    * done in a different manner from painting. However, the implementation
    * in this class simply calls the <code>paint()</code> method.
    *
    * @param g the graphics context of the print device
    * 
    * @see #paint(Graphics)
    */
Index: java/awt/Container.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/Container.java,v
retrieving revision 1.101
diff -u -1 -2 -r1.101 Container.java
--- java/awt/Container.java	25 Jul 2006 13:21:04 -0000	1.101
+++ java/awt/Container.java	27 Jul 2006 14:28:46 -0000
@@ -80,29 +80,24 @@
    * Compatible with JDK 1.0+.
    */
   private static final long serialVersionUID = 4613797578919906343L;
 
   /* Serialized fields from the serialization spec. */
   int ncomponents;
   Component[] component;
   LayoutManager layoutMgr;
 
   Dimension maxSize;
 
   /**
-   * Keeps track if the Container was cleared during a paint/update.
-   */
-  private boolean backCleared;
-
-  /**
    * @since 1.4
    */
   boolean focusCycleRoot;
 
   /**
    * Indicates if this container provides a focus traversal policy.
    *
    * @since 1.5
    */
   private boolean focusTraversalPolicyProvider;
 
   int containerSerializedDataVersion;
@@ -850,31 +845,28 @@
 
   /**
    * Paints this container.  The implementation of this method in this
    * class forwards to any lightweight components in this container.  If
    * this method is subclassed, this method should still be invoked as
    * a superclass method so that lightweight components are properly
    * drawn.
    *
    * @param g - The graphics context for this paint job.
    */
   public void paint(Graphics g)
   {
-    if (!isShowing())
-      return;
-
-    // Visit heavyweights if the background was cleared
-    // for this container.
-    visitChildren(g, GfxPaintVisitor.INSTANCE, !backCleared);
-    backCleared = false;
+    if (isShowing())
+      {
+        visitChildren(g, GfxPaintVisitor.INSTANCE, true);
+      }
   }
 
   /**
    * Updates this container.  The implementation of this method in this
    * class forwards to any lightweight components in this container.  If
    * this method is subclassed, this method should still be invoked as
    * a superclass method so that lightweight components are properly
    * drawn.
    *
    * @param g The graphics context for this update.
    *
    * @specnote The specification suggests that this method forwards the
@@ -886,32 +878,33 @@
    *           be painted.
    */
   public void update(Graphics g)
   {
     // It seems that the JDK clears the background of containers like Panel
     // and Window (within this method) but not of 'plain' Containers or
     // JComponents. This could
     // lead to the assumption that it only clears heavyweight containers.
     // However that is not quite true. In a test with a custom Container
     // that overrides isLightweight() to return false, the background is
     // also not cleared. So we do a check on !(peer instanceof LightweightPeer)
     // instead.
-    ComponentPeer p = peer;
-    if (p != null && ! (p instanceof LightweightPeer))
+    if (isShowing())
       {
-        g.clearRect(0, 0, getWidth(), getHeight());
-        backCleared = true;
+        ComponentPeer p = peer;
+        if (! (p instanceof LightweightPeer))
+          {
+            g.clearRect(0, 0, getWidth(), getHeight());
+          }
+        paint(g);
       }
-
-    paint(g);
   }
 
   /**
    * Prints this container.  The implementation of this method in this
    * class forwards to any lightweight components in this container.  If
    * this method is subclassed, this method should still be invoked as
    * a superclass method so that lightweight components are properly
    * drawn.
    *
    * @param g The graphics context for this print job.
    */
   public void print(Graphics g)

Reply via email to