I noticed that we didn't support Hierarchy(Bounds)Listeners at all in
our AWT/Swing. I implemented support for this.
One problem with HierarchyEvents is that they get dispatched not only to
the source component (like most other events) but must be propagated
through all children and their children and so on. This is not what we
normally want, because HierarchyEvents are fired on each component
reshape/add/remove/show/hide. I optimized this by adding counters that
keep track how many listeners are actually installed in the current
component (plus subtree). This way we only have to walk down the paths
that actually have listeners (which normally are none at all, or very few).
2006-07-14 Roman Kennke <[EMAIL PROTECTED]>
* java/awt/Component.java
(numHierarchyListeners): New field.
(numHierarchyBoundsListeners): New field.
(show): Fire hierarchy events here. Only fire component event
if there is actually a listener for it.
(hide): Fire hierarchy events here. Only fire component event
if there is actually a listener for it.
(reshape): Fire hierarchy events here. Only fire component event
if there is actually a listener for it.
(addHierarchyListeners): Update listener counters.
(removeHierarchyListeners): Update listener counters.
(addHierarchyBoundsListeners): Update listener counters.
(removeHierarchyBoundsListeners): Update listener counters.
(fireHierarchyEvent): New helper method for firing hierarchy
events.
* java/awt/Container.java
(addImpl): Update listener counters. Fire hierarchy event.
(remove): Update listener counters. Fire hierarchy event.
(fireHierarchyEvent): New helper method for firing hierarchy
events.
(updateHierarchyListenerCount): New helper method for
updating the listener counters.
/Roman
Index: java/awt/Component.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/Component.java,v
retrieving revision 1.133
diff -u -1 -2 -r1.133 Component.java
--- java/awt/Component.java 13 Jul 2006 19:27:06 -0000 1.133
+++ java/awt/Component.java 14 Jul 2006 10:26:34 -0000
@@ -585,24 +585,35 @@
* @since 1.4
*/
transient GraphicsConfiguration graphicsConfig;
/**
* The buffer strategy for repainting.
*
* @since 1.4
*/
transient BufferStrategy bufferStrategy;
/**
+ * The number of hierarchy listeners of this container plus all of its
+ * children. This is needed for efficient handling of HierarchyEvents.
+ * These must be propagated to all child components with HierarchyListeners
+ * attached. To avoid traversal of the whole subtree, we keep track of
+ * the number of HierarchyListeners here and only walk the paths that
+ * actually have listeners.
+ */
+ int numHierarchyListeners;
+ int numHierarchyBoundsListeners;
+
+ /**
* true if requestFocus was called on this component when its
* top-level ancestor was not focusable.
*/
private transient FocusEvent pendingFocusRequest = null;
/**
* The system properties that affect image updating.
*/
private static transient boolean incrementalDraw;
private static transient Long redrawRate;
static
@@ -929,39 +940,53 @@
{
// We must set visible before showing the peer. Otherwise the
// peer could post paint events before visible is true, in which
// case lightweight components are not initially painted --
// Container.paint first calls isShowing () before painting itself
// and its children.
if(!isVisible())
{
this.visible = true;
// Avoid NullPointerExceptions by creating a local reference.
ComponentPeer currentPeer=peer;
if (currentPeer != null)
+ {
currentPeer.show();
- // The JDK repaints the component before invalidating the parent.
- // So do we.
- if (isShowing() && isLightweight())
- repaint();
+
+ // Fire HierarchyEvent.
+ fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED,
+ this, parent,
+ HierarchyEvent.SHOWING_CHANGED);
+
+ // The JDK repaints the component before invalidating the parent.
+ // So do we.
+ if (isLightweight())
+ repaint();
+ }
// Invalidate the parent if we have one. The component itself must
// not be invalidated. We also avoid NullPointerException with
// a local reference here.
Container currentParent = parent;
if (currentParent != null)
currentParent.invalidate();
- ComponentEvent ce =
- new ComponentEvent(this,ComponentEvent.COMPONENT_SHOWN);
- getToolkit().getSystemEventQueue().postEvent(ce);
+ // 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)
+ {
+ ComponentEvent ce =
+ new ComponentEvent(this,ComponentEvent.COMPONENT_SHOWN);
+ getToolkit().getSystemEventQueue().postEvent(ce);
+ }
}
}
/**
* Makes this component visible or invisible.
*
* @param visible true to make this component visible
*
* @deprecated use [EMAIL PROTECTED] #setVisible(boolean)} instead
*/
public void show(boolean visible)
{
@@ -974,42 +999,56 @@
/**
* Hides this component so that it is no longer shown on the screen.
*
* @deprecated use [EMAIL PROTECTED] #setVisible(boolean)} instead
*/
public void hide()
{
if (isVisible())
{
// Avoid NullPointerExceptions by creating a local reference.
ComponentPeer currentPeer=peer;
if (currentPeer != null)
- currentPeer.setVisible(false);
+ {
+ currentPeer.hide();
+
+ // Fire hierarchy event.
+ fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED,
+ this, parent,
+ HierarchyEvent.SHOWING_CHANGED);
+ }
+
boolean wasShowing = isShowing();
this.visible = false;
// The JDK repaints the component before invalidating the parent.
// So do we.
if (wasShowing)
repaint();
// Invalidate the parent if we have one. The component itself must
// not be invalidated. We also avoid NullPointerException with
// a local reference here.
Container currentParent = parent;
if (currentParent != null)
currentParent.invalidate();
- ComponentEvent ce =
- new ComponentEvent(this,ComponentEvent.COMPONENT_HIDDEN);
- getToolkit().getSystemEventQueue().postEvent(ce);
+ // 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)
+ {
+ ComponentEvent ce =
+ new ComponentEvent(this,ComponentEvent.COMPONENT_HIDDEN);
+ getToolkit().getSystemEventQueue().postEvent(ce);
+ }
}
}
/**
* Returns this component's foreground color. If not set, this is inherited
* from the parent.
*
* @return this component's foreground color, or null
* @see #setForeground(Color)
*/
public Color getForeground()
{
@@ -1440,38 +1479,71 @@
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);
}
}
- // Only post event if this component is visible and has changed size.
- if (isShowing ()
- && (oldx != x || oldy != y))
- {
- ComponentEvent ce = new ComponentEvent(this,
- ComponentEvent.COMPONENT_MOVED);
- getToolkit().getSystemEventQueue().postEvent(ce);
- }
- if (isShowing ()
- && (oldwidth != width || oldheight != height))
- {
- ComponentEvent ce = new ComponentEvent(this,
- ComponentEvent.COMPONENT_RESIZED);
- getToolkit().getSystemEventQueue().postEvent(ce);
+ 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)
+ {
+ // Fire component event on this component.
+ if (moved)
+ {
+ 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)
+ {
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ {
+ Component child = cont.getComponent(i);
+ child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_RESIZED,
+ this, parent, 0);
+ }
+ }
+ if (moved)
+ {
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ {
+ 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
* @throws NullPointerException if r is null
* @see #getBounds()
* @see #setLocation(Point)
@@ -2665,39 +2737,47 @@
*
* @param listener the new listener to add
* @see HierarchyEvent
* @see #removeHierarchyListener(HierarchyListener)
* @see #getHierarchyListeners()
* @since 1.3
*/
public synchronized void addHierarchyListener(HierarchyListener listener)
{
hierarchyListener = AWTEventMulticaster.add(hierarchyListener, listener);
if (hierarchyListener != null)
enableEvents(AWTEvent.HIERARCHY_EVENT_MASK);
+
+ numHierarchyListeners++;
+ if (parent != null)
+ parent.updateHierarchyListenerCount(AWTEvent.HIERARCHY_EVENT_MASK, 1);
}
/**
* Removes the specified listener from the component. This is harmless if
* the listener was not previously registered.
*
* @param listener the listener to remove
* @see HierarchyEvent
* @see #addHierarchyListener(HierarchyListener)
* @see #getHierarchyListeners()
* @since 1.3
*/
public synchronized void removeHierarchyListener(HierarchyListener listener)
{
hierarchyListener = AWTEventMulticaster.remove(hierarchyListener, listener);
+
+ numHierarchyListeners--;
+ if (parent != null)
+ parent.updateHierarchyListenerCount(AWTEvent.HIERARCHY_EVENT_MASK, -1);
}
/**
* Returns an array of all specified listeners registered on this component.
*
* @return an array of listeners
* @see #addHierarchyListener(HierarchyListener)
* @see #removeHierarchyListener(HierarchyListener)
* @since 1.4
*/
public synchronized HierarchyListener[] getHierarchyListeners()
{
@@ -2715,59 +2795,103 @@
* @see HierarchyEvent
* @see #removeHierarchyBoundsListener(HierarchyBoundsListener)
* @see #getHierarchyBoundsListeners()
* @since 1.3
*/
public synchronized void
addHierarchyBoundsListener(HierarchyBoundsListener listener)
{
hierarchyBoundsListener =
AWTEventMulticaster.add(hierarchyBoundsListener, listener);
if (hierarchyBoundsListener != null)
enableEvents(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK);
+
+ numHierarchyBoundsListeners++;
+ if (parent != null)
+ parent.updateHierarchyListenerCount(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK,
+ 1);
}
/**
* Removes the specified listener from the component. This is harmless if
* the listener was not previously registered.
*
* @param listener the listener to remove
* @see HierarchyEvent
* @see #addHierarchyBoundsListener(HierarchyBoundsListener)
* @see #getHierarchyBoundsListeners()
* @since 1.3
*/
public synchronized void
removeHierarchyBoundsListener(HierarchyBoundsListener listener)
{
hierarchyBoundsListener =
AWTEventMulticaster.remove(hierarchyBoundsListener, listener);
+
+ numHierarchyBoundsListeners--;
+ if (parent != null)
+ parent.updateHierarchyListenerCount(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK,
+ -1);
}
/**
* Returns an array of all specified listeners registered on this component.
*
* @return an array of listeners
* @see #addHierarchyBoundsListener(HierarchyBoundsListener)
* @see #removeHierarchyBoundsListener(HierarchyBoundsListener)
* @since 1.4
*/
public synchronized HierarchyBoundsListener[] getHierarchyBoundsListeners()
{
return (HierarchyBoundsListener[])
AWTEventMulticaster.getListeners(hierarchyBoundsListener,
HierarchyBoundsListener.class);
}
/**
+ * Fires a HierarchyEvent or HierarchyChangeEvent on this component.
+ *
+ * @param id the event id
+ * @param changed the changed component
+ * @param parent the parent
+ * @param flags the event flags
+ */
+ void fireHierarchyEvent(int id, Component changed, Container parent,
+ long flags)
+ {
+ boolean enabled = false;
+ switch (id)
+ {
+ case HierarchyEvent.HIERARCHY_CHANGED:
+ enabled = hierarchyListener != null
+ || (eventMask & AWTEvent.HIERARCHY_EVENT_MASK) != 0;
+ break;
+ case HierarchyEvent.ANCESTOR_MOVED:
+ case HierarchyEvent.ANCESTOR_RESIZED:
+ enabled = hierarchyBoundsListener != null
+ || (eventMask & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0;
+ break;
+ default:
+ assert false : "Should not reach here";
+ }
+ if (enabled)
+ {
+ HierarchyEvent ev = new HierarchyEvent(this, id, changed, parent,
+ flags);
+ dispatchEvent(ev);
+ }
+ }
+
+ /**
* Adds the specified listener to this component. This is harmless if the
* listener is null, but if the listener has already been registered, it
* will now be registered twice.
*
* @param listener the new listener to add
* @see KeyEvent
* @see #removeKeyListener(KeyListener)
* @see #getKeyListeners()
* @since 1.1
*/
public synchronized void addKeyListener(KeyListener listener)
{
Index: java/awt/Container.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/Container.java,v
retrieving revision 1.98
diff -u -1 -2 -r1.98 Container.java
--- java/awt/Container.java 13 Jul 2006 19:27:06 -0000 1.98
+++ java/awt/Container.java 14 Jul 2006 10:26:34 -0000
@@ -33,24 +33,25 @@
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package java.awt;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
+import java.awt.event.HierarchyEvent;
import java.awt.event.KeyEvent;
import java.awt.peer.ComponentPeer;
import java.awt.peer.ContainerPeer;
import java.awt.peer.LightweightPeer;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Collections;
@@ -353,24 +354,34 @@
}
if (index == -1)
component[ncomponents++] = comp;
else
{
System.arraycopy(component, index, component, index + 1,
ncomponents - index);
component[index] = comp;
++ncomponents;
}
+ // Update the counter for Hierarchy(Bounds)Listeners.
+ int childHierarchyListeners = comp.numHierarchyListeners;
+ if (childHierarchyListeners > 0)
+ updateHierarchyListenerCount(AWTEvent.HIERARCHY_EVENT_MASK,
+ childHierarchyListeners);
+ int childHierarchyBoundsListeners = comp.numHierarchyBoundsListeners;
+ if (childHierarchyBoundsListeners > 0)
+ updateHierarchyListenerCount(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK,
+ childHierarchyListeners);
+
// Notify the layout manager.
if (layoutMgr != null)
{
// If we have a LayoutManager2 the constraints are "real",
// otherwise they are the "name" of the Component to add.
if (layoutMgr instanceof LayoutManager2)
{
LayoutManager2 lm2 = (LayoutManager2) layoutMgr;
lm2.addLayoutComponent(comp, constraints);
}
else if (constraints instanceof String)
layoutMgr.addLayoutComponent((String) constraints, comp);
@@ -379,63 +390,81 @@
}
// We previously only sent an event when this container is showing.
// Also, the event was posted to the event queue. A Mauve test shows
// that this event is not delivered using the event queue and it is
// also sent when the container is not showing.
ContainerEvent ce = new ContainerEvent(this,
ContainerEvent.COMPONENT_ADDED,
comp);
ContainerListener[] listeners = getContainerListeners();
for (int i = 0; i < listeners.length; i++)
listeners[i].componentAdded(ce);
+
+ // Notify hierarchy listeners.
+ comp.fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED, comp,
+ this, HierarchyEvent.PARENT_CHANGED);
}
}
/**
* Removes the component at the specified index from this container.
*
* @param index The index of the component to remove.
*/
public void remove(int index)
{
synchronized (getTreeLock ())
{
Component r = component[index];
ComponentListener[] list = r.getComponentListeners();
for (int j = 0; j < list.length; j++)
r.removeComponentListener(list[j]);
r.removeNotify();
System.arraycopy(component, index + 1, component, index,
ncomponents - index - 1);
component[--ncomponents] = null;
+ // Update the counter for Hierarchy(Bounds)Listeners.
+ int childHierarchyListeners = r.numHierarchyListeners;
+ if (childHierarchyListeners > 0)
+ updateHierarchyListenerCount(AWTEvent.HIERARCHY_EVENT_MASK,
+ -childHierarchyListeners);
+ int childHierarchyBoundsListeners = r.numHierarchyBoundsListeners;
+ if (childHierarchyBoundsListeners > 0)
+ updateHierarchyListenerCount(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK,
+ -childHierarchyListeners);
+
invalidate();
if (layoutMgr != null)
layoutMgr.removeLayoutComponent(r);
r.parent = null;
if (isShowing ())
{
// Post event to notify of removing the component.
ContainerEvent ce = new ContainerEvent(this,
- ContainerEvent.COMPONENT_REMOVED,
- r);
+ ContainerEvent.COMPONENT_REMOVED,
+ r);
getToolkit().getSystemEventQueue().postEvent(ce);
}
+
+ // Notify hierarchy listeners.
+ r.fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED, r,
+ this, HierarchyEvent.PARENT_CHANGED);
}
}
/**
* Removes the specified component from this container.
*
* @param comp The component to remove from this container.
*/
public void remove(Component comp)
{
synchronized (getTreeLock ())
{
@@ -1870,24 +1899,66 @@
c = c.findNextFocusComponent(null);
if (c != null)
return c;
}
else if (component[j].isFocusTraversable())
return component[j];
}
return null;
}
}
+ /**
+ * Fires hierarchy events to the children of this container and this
+ * container itself. This overrides [EMAIL PROTECTED] Component#fireHierarchyEvent}
+ * in order to forward this event to all children.
+ */
+ void fireHierarchyEvent(int id, Component changed, Container parent,
+ long flags)
+ {
+ // Only propagate event if there is actually a listener waiting for it.
+ if ((id == HierarchyEvent.HIERARCHY_CHANGED && numHierarchyListeners > 0)
+ || ((id == HierarchyEvent.ANCESTOR_MOVED
+ || id == HierarchyEvent.ANCESTOR_RESIZED)
+ && numHierarchyBoundsListeners > 0))
+ {
+ for (int i = 0; i < ncomponents; i++)
+ component[i].fireHierarchyEvent(id, changed, parent, flags);
+ super.fireHierarchyEvent(id, changed, parent, flags);
+ }
+ }
+
+ /**
+ * Adjusts the number of hierarchy listeners of this container and all of
+ * its parents. This is called by the add/remove listener methods and
+ * structure changing methods in Container.
+ *
+ * @param type the type, either [EMAIL PROTECTED] AWTEvent#HIERARCHY_BOUNDS_EVENT_MASK}
+ * or [EMAIL PROTECTED] AWTEvent#HIERARCHY_EVENT_MASK}
+ * @param delta the number of listeners added or removed
+ */
+ void updateHierarchyListenerCount(long type, int delta)
+ {
+ if (type == AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK)
+ numHierarchyBoundsListeners += delta;
+ else if (type == AWTEvent.HIERARCHY_EVENT_MASK)
+ numHierarchyListeners += delta;
+ else
+ assert false : "Should not reach here";
+
+ if (parent != null)
+ parent.updateHierarchyListenerCount(type, delta);
+ }
+
private void addNotifyContainerChildren()
{
synchronized (getTreeLock ())
{
for (int i = ncomponents; --i >= 0; )
{
component[i].addNotify();
if (component[i].isLightweight ())
{
enableEvents(component[i].eventMask);
if (peer != null && !isLightweight ())
enableEvents (AWTEvent.PAINT_EVENT_MASK);