This fixes lightweight dispatching once more. There have been some
issues with Espial/Espresso, which seems to depend on some very special
behaviour:
- Mouse events are dispatched to a component if the component listens
for ANY mouse event, not necessarily for the specific mouse event beeing
dispatched. That means that a mouse pressed must be dispatched to a
component that only listenes for mouse move events, but not for mouse
pressed events.
- Espresso overrides some methods in Container (namely
getComponentCount() and getComponent() - but not getComponents() -) to
hide some internal components (the tabs in their TabbedPane
implementation). These tabs could not be clicked because the lightweight
dispatcher didn't see them. There's the alternative between using
getComponents() or accessing the Container fields directly. I made it
access the fields directly, because getComponents() creates and copies
the array.
Also I made some improvements:
- I broke up the LightweightDispatcher methods into smaller pieces. This
should make the class more readable.
- I improved the algorithm in LightweightDispatcher to avoid unnecessary
traversal of the component hierarchy.
2006-09-22 Roman Kennke <[EMAIL PROTECTED]>
* java/awt/Component.java
(enableEvents): Set newEventsOnly flag.
* java/awt/Container.java
(dispatchEventImpl): Consume event if lightweight dispatcher
dispatched the event. Don't call processEvent() here, this
is already done in Component.dispatchEventImpl(). For
heavyweights or when the lightweight dispatcher could
not dispatch, fall back to calling super.
(dispatchNoLightweight): New helper method to avoid
recursivly calling the lightweight dispatcher.
* java/awt/LightweightDispatcher.java
(dragButton): Removed field.
(dragTarget): Removed field.
(mouseEventTarget): New field.
(convertPointToChild): Removed method.
(dispatchEvent): Don't depend on component beeing
a window.
(findTarget): Improved algorithm for finding a target.
Before we went down to the deepest component and went
up again to find a suitable target. Now we go
down only once, without going up.
(handleMouseEvent): Broke method down into some smaller
helper methods.
(isDragging): New helper method.
(isMouseListening): New helper method.
(redispatch): New helper method.
(trackEnterExit): New helper method.
/Roman
Index: java/awt/Component.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/Component.java,v
retrieving revision 1.148
diff -u -1 -5 -r1.148 Component.java
--- java/awt/Component.java 20 Sep 2006 09:12:25 -0000 1.148
+++ java/awt/Component.java 22 Sep 2006 12:18:14 -0000
@@ -3505,30 +3505,31 @@
if ((eventsToEnable & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0
&& (eventMask & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) == 0)
{
// Need to lock the tree, otherwise we might end up inconsistent.
synchronized (getTreeLock())
{
numHierarchyBoundsListeners++;
if (parent != null)
parent.updateHierarchyListenerCount
(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK,
1);
}
}
eventMask |= eventsToEnable;
+ newEventsOnly = true;
// Only heavyweight peers handle this.
ComponentPeer p = peer;
Component comp = this;
while (p instanceof LightweightPeer)
{
comp = comp.parent;
p = comp == null ? null : comp.peer;
}
if (p != null)
p.setEventMask(eventMask);
}
@@ -5683,31 +5684,30 @@
((ActionEvent) e).getActionCommand ());
return translated;
}
/**
* Implementation of dispatchEvent. Allows trusted package classes
* to dispatch additional events first. This implementation first
* translates <code>e</code> to an AWT 1.0 event and sends the
* result to [EMAIL PROTECTED] #postEvent}. If the AWT 1.0 event is not
* handled, and events of type <code>e</code> are enabled for this
* component, e is passed on to [EMAIL PROTECTED] #processEvent}.
*
* @param e the event to dispatch
*/
-
void dispatchEventImpl(AWTEvent e)
{
// Retarget focus events before dispatching it to the KeyboardFocusManager
// in order to handle lightweight components properly.
boolean dispatched = false;
if (! e.isFocusManagerEvent)
{
e = KeyboardFocusManager.retargetFocusEvent(e);
dispatched = KeyboardFocusManager.getCurrentKeyboardFocusManager()
.dispatchEvent(e);
}
if (! dispatched)
{
// Give toolkit a chance to dispatch the event
Index: java/awt/Container.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/Container.java,v
retrieving revision 1.107
diff -u -1 -5 -r1.107 Container.java
--- java/awt/Container.java 20 Sep 2006 12:35:41 -0000 1.107
+++ java/awt/Container.java 22 Sep 2006 12:18:14 -0000
@@ -1873,47 +1873,67 @@
if(!gfx.hitClip(bounds.x,bounds.y, bounds.width, bounds.height))
return;
Graphics g2 = gfx.create(bounds.x, bounds.y, bounds.width,
bounds.height);
try
{
g2.setFont(comp.getFont());
visitor.visit(comp, g2);
}
finally
{
g2.dispose();
}
}
+ /**
+ * Overridden to dispatch events to lightweight descendents.
+ *
+ * @param e the event to dispatch.
+ */
void dispatchEventImpl(AWTEvent e)
{
- boolean dispatched =
- LightweightDispatcher.getInstance().dispatchEvent(e);
- if (! dispatched)
+ LightweightDispatcher dispatcher = LightweightDispatcher.getInstance();
+ if (! isLightweight() && dispatcher.dispatchEvent(e))
{
- if ((e.id <= ContainerEvent.CONTAINER_LAST
- && e.id >= ContainerEvent.CONTAINER_FIRST)
- && (containerListener != null
- || (eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0))
- processEvent(e);
- else
- super.dispatchEventImpl(e);
+ // Some lightweight descendent got this event dispatched. Consume
+ // it and let the peer handle it.
+ e.consume();
+ ComponentPeer p = peer;
+ if (p != null)
+ p.handleEvent(e);
+ }
+ else
+ {
+ super.dispatchEventImpl(e);
}
}
/**
+ * This is called by the lightweight dispatcher to avoid recursivly
+ * calling into the lightweight dispatcher.
+ *
+ * @param e the event to dispatch
+ *
+ * @see LightweightDispatcher#redispatch(MouseEvent, Component, int)
+ */
+ void dispatchNoLightweight(AWTEvent e)
+ {
+ super.dispatchEventImpl(e);
+ }
+
+ /**
* Tests if this container has an interest in the given event id.
*
* @param eventId The event id to check.
*
* @return <code>true</code> if a listener for the event id exists or
* if the eventMask is set for the event id.
*
* @see java.awt.Component#eventTypeEnabled(int)
*/
boolean eventTypeEnabled(int eventId)
{
if(eventId <= ContainerEvent.CONTAINER_LAST
&& eventId >= ContainerEvent.CONTAINER_FIRST)
return containerListener != null
|| (eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0;
Index: java/awt/LightweightDispatcher.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/LightweightDispatcher.java,v
retrieving revision 1.15
diff -u -1 -5 -r1.15 LightweightDispatcher.java
--- java/awt/LightweightDispatcher.java 18 Sep 2006 13:46:09 -0000 1.15
+++ java/awt/LightweightDispatcher.java 22 Sep 2006 12:18:14 -0000
@@ -26,72 +26,66 @@
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
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.InputEvent;
import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.peer.LightweightPeer;
import java.util.WeakHashMap;
/**
* Redispatches mouse events to lightweight components. The native peers know
* nothing about the lightweight components and thus mouse events are always
* targetted at Windows or heavyweight components. This class listenes directly
* on the eventqueue and dispatches mouse events to lightweight components.
*
* @author Roman Kennke ([EMAIL PROTECTED])
*/
-class LightweightDispatcher
+final class LightweightDispatcher
{
/**
* Maps thread groups to lightweight dispatcher instances. We need to
* have one instance per thread group so that 2 or more applets or otherwise
* separated applications (like in OSGI) do not interfer with each other.
*/
private static WeakHashMap instances = new WeakHashMap();
/**
- * The component that is the start of a mouse dragging. All MOUSE_DRAGGED
- * events that follow the initial press must have the source set to this,
- * as well as the MOUSE_RELEASED event following the dragging.
- */
- private Component dragTarget;
-
- /**
- * Stores the button number which started the drag operation. This is needed
- * because we want to handle only one drag operation and only the button that
- * started the dragging should be able to stop it (by a button release).
- */
- private int dragButton;
-
- /**
* The last mouse event target. If the target changes, additional
* MOUSE_ENTERED and MOUSE_EXITED events must be dispatched.
*/
private Component lastTarget;
/**
+ * The current mouseEventTarget.
+ */
+ private Component mouseEventTarget;
+
+ /**
* Returns an instance of LightweightDispatcher for the current thread's
* thread group.
*
* @return an instance of LightweightDispatcher for the current thread's
* thread group
*/
static LightweightDispatcher getInstance()
{
Thread t = Thread.currentThread();
ThreadGroup tg = t.getThreadGroup();
LightweightDispatcher instance = (LightweightDispatcher) instances.get(tg);
if (instance == null)
{
instance = new LightweightDispatcher();
instances.put(tg, instance);
@@ -101,241 +95,265 @@
/**
* Creates a new LightweightDispatcher. This is private to prevent access
* from outside. Use [EMAIL PROTECTED] #getInstance()} instead.
*/
private LightweightDispatcher()
{
// Nothing to do here.
}
/**
* Receives notification if a mouse event passes along the eventqueue.
*
* @param event the event
*/
- public boolean dispatchEvent(AWTEvent event)
+ public boolean dispatchEvent(final AWTEvent event)
{
- if (event instanceof MouseEvent && event.getSource() instanceof Window)
+ if (event instanceof MouseEvent)
{
MouseEvent mouseEvent = (MouseEvent) event;
return handleMouseEvent(mouseEvent);
}
return false;
}
/**
* Handles all mouse events that are targetted at toplevel containers
* (Window instances) and dispatches them to the correct lightweight child.
*
* @param ev the mouse event
* @return whether or not we found a lightweight that handled the event.
*/
- private boolean handleMouseEvent(MouseEvent ev)
+ private boolean handleMouseEvent(final MouseEvent ev)
{
- Window window = (Window) ev.getSource();
- // Find the target for the mouse event. We first seach the deepest
- // component at the specified location. The we go up to its parent and
- // try to find a neighbor of the deepest component that is suitable as
- // mouse event target (it must be showing, at that location and have either
- // a MouseListener or MouseMotionListener installed). If no such component
- // is found, then we walk up the container hierarchy and find the next
- // container that has a MouseListener or MouseMotionListener installed.
- Component deepest = window.findComponentAt(ev.getX(), ev.getY());
- if (deepest == null)
- return false;
- Container parent = deepest.getParent();
- Point loc = ev.getPoint();
- loc = convertPointToChild(window, loc, parent);
- Component target = deepest;
- if (parent != null)
- {
- Component newTarget = findTarget(parent, loc, ev.getID());
- while (newTarget == null && parent != null)
- {
- if (parent.eventTypeEnabled(ev.getID()))
- {
- newTarget = parent;
- }
- else
- parent = parent.getParent();
- }
- if (newTarget != null)
- target = newTarget;
- }
- if (target == null || target.isLightweight())
+ Container container = (Container) ev.getSource();
+ Component target = findTarget(container, ev.getX(), ev.getY());
+ trackEnterExit(target, ev);
+ int id = ev.getID();
+
+ // Dont update the mouseEventTarget when dragging. Also, MOUSE_CLICKED
+ // must be dispatched to the original target of MOUSE_PRESSED, so don't
+ // update in this case either.
+ if (! isDragging(ev) && id != MouseEvent.MOUSE_CLICKED)
+ mouseEventTarget = (target != container) ? target : null;
+
+ if (mouseEventTarget != null)
{
- // Dispatch additional MOUSE_EXITED and MOUSE_ENTERED if event target
- // is different from the last event target.
- if (target != lastTarget)
+ switch (id)
{
- if (lastTarget != null)
- {
- Point p1 = convertPointToChild(window, ev.getPoint(),
- lastTarget);
- MouseEvent mouseExited =
- new MouseEvent(lastTarget, MouseEvent.MOUSE_EXITED,
- ev.getWhen(), ev.getModifiers(), p1.x, p1.y,
- ev.getClickCount(), ev.isPopupTrigger());
- lastTarget.dispatchEvent(mouseExited);
- }
-
- // If a target exists dispatch the MOUSE_ENTERED event.
- // Experimenting shows that the MOUSE_ENTERED is also dispatched
- // when the mouse is dragging.
- if (target != null)
- {
- Point p = convertPointToChild(window, ev.getPoint(), target);
- MouseEvent mouseEntered =
- new MouseEvent(target, MouseEvent.MOUSE_ENTERED, ev.getWhen(),
- ev.getModifiers(), p.x, p.y, ev.getClickCount(),
- ev.isPopupTrigger());
- target.dispatchEvent(mouseEntered);
- }
- }
-
- switch (ev.getID())
- {
- case MouseEvent.MOUSE_PRESSED:
- // Handle the start of a drag operation or discard the event if
- // one is already in progress. This prevents focus changes with the
- // other mouse buttons when one is used for dragging.
- if (dragTarget == null)
- {
- lastTarget = dragTarget = target;
-
- // Save the button that started the drag operation.
- dragButton = ev.getButton();
- }
- else
- return false;
-
+ case MouseEvent.MOUSE_ENTERED:
+ case MouseEvent.MOUSE_EXITED:
+ // This is already handled in trackEnterExit().
break;
+ case MouseEvent.MOUSE_PRESSED:
case MouseEvent.MOUSE_RELEASED:
- // Stop the drag operation only when the button that started
- // it was released.
- if (dragTarget != null && dragButton == ev.getButton())
- {
- // Only post MOUSE_RELEASED to dragTarget (set in
- // MOUSE_PRESSED) when the dragTarget is actually visible.
- // Otherwise post the event to the normal target.
- if (dragTarget.isVisible())
- target = dragTarget;
- dragTarget = null;
- }
-
- lastTarget = target;
+ case MouseEvent.MOUSE_MOVED:
+ redispatch(ev, mouseEventTarget, id);
break;
case MouseEvent.MOUSE_CLICKED:
- // When we receive a MOUSE_CLICKED, we set the target to the
- // previous target, which must have been a MOUSE_RELEASED event.
- // This is necessary for the case when the MOUSE_RELEASED has
- // caused the original target (like an internal component) go
- // away.
- // This line is the reason why it is not possible to move the
- // 'lastTarget = target' assignment before the switch-statement.
- target = lastTarget;
+ // MOUSE_CLICKED must be dispatched to the original target of
+ // MOUSE_PRESSED.
+ if (target == mouseEventTarget)
+ redispatch(ev, mouseEventTarget, id);
break;
case MouseEvent.MOUSE_DRAGGED:
- // We consider only dragTarget for redispatching the event still
- // we have to act in a way that the newly found target component
- // was handled.
- lastTarget = target;
- target = dragTarget;
- break;
- default:
- // Only declare current target as the old value in all other
- // cases.
- lastTarget = target;
+ if (isDragging(ev))
+ redispatch(ev, mouseEventTarget, id);
break;
- }
-
- if (target != null)
- {
- Point targetCoordinates = convertPointToChild(window,
- ev.getPoint(),
- target);
- int dx = targetCoordinates.x - ev.getX();
- int dy = targetCoordinates.y - ev.getY();
- ev.translatePoint(dx, dy);
- ev.setSource(target);
- target.dispatchEvent(ev);
-
- // We reset the event, so that the normal event dispatching is not
- // influenced by this modified event.
- ev.setSource(window);
- ev.translatePoint(-dx, -dy);
}
-
- return true;
+ ev.consume();
}
- else
- return false;
+
+ return ev.isConsumed();
}
/**
* Finds the actual target for a mouseevent, starting at <code>c</code>.
* This searches through the children of the container and finds the first
* one which is showing, at the location from the mouse event and has
* a MouseListener or MouseMotionListener attached. If no such child component
* is found, null is returned.
*
* @param c the container to search through
* @param loc the mouse event point
*
* @return the actual receiver of the mouse event, or null, if no such
* component has been found
*/
- private Component findTarget(Container c, Point loc, int id)
+ private Component findTarget(final Container c, final int x, final int y)
{
- int numComponents = c.getComponentCount();
Component target = null;
- if (c != null)
+
+ // First we check the children of the container.
+
+ // Note: It is important that we use the package private Container
+ // fields ncomponents and component here. There are applications
+ // that override getComponentCount()
+ // and getComponent() to hide internal components, which makes
+ // the LightweightDispatcher not work correctly in these cases.
+ // As a positive sideeffect this is slightly more efficient.
+ int nChildren = c.ncomponents;
+ for (int i = 0; i < nChildren && target == null; i++)
{
- int childX;
- int childY;
- for (int i = 0; i < numComponents; i++)
+ Component child = c.component[i];
+ int childX = x - child.x;
+ int childY = y - child.y;
+ if (child != null && child.visible
+ && child.peer instanceof LightweightPeer
+ && child.contains(childX, childY))
{
- Component child = c.getComponent(i);
- if (child.isShowing())
+ // Check if there's a deeper possible target.
+ if (child instanceof Container)
{
- childX = loc.x - child.getX();
- childY = loc.y - child.getY();
- if (child.contains(childX, childY)
- && child.eventTypeEnabled(id))
- {
- target = child;
- break;
- }
+ Component deeper = findTarget((Container) child,
+ childX, childY);
+ if (deeper != null)
+ target = deeper;
}
+ // Check if the child itself is interested in mouse events.
+ else if (isMouseListening(child))
+ target = child;
}
}
+
+ // Check the container itself, if we didn't find a target yet.
+ if (target == null && c.contains(x, y) && isMouseListening(c))
+ target = c;
+
return target;
}
+
+ /**
+ * Checks if the specified component would be interested in a mouse event.
+ *
+ * @param c the component to check
+ *
+ * @return <code>true</code> if the component has mouse listeners installed,
+ * <code>false</code> otherwise
+ */
+ private boolean isMouseListening(final Component c)
+ {
+ // Note: It is important to NOT check if the component is listening
+ // for a specific event (for instance, mouse motion events). The event
+ // gets dispatched to the component if the component is listening
+ // for ANY mouse event, even when the component is not listening for the
+ // specific type of event. There are applications that depend on this
+ // (sadly).
+ return c.mouseListener != null
+ || c.mouseMotionListener != null
+ || c.mouseWheelListener != null
+ || (c.eventMask & AWTEvent.MOUSE_EVENT_MASK) != 0
+ || (c.eventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0
+ || (c.eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0;
+ }
+
+ /**
+ * Tracks MOUSE_ENTERED and MOUSE_EXIT as well as MOUSE_MOVED and
+ * MOUSE_DRAGGED and creates synthetic MOUSE_ENTERED and MOUSE_EXITED for
+ * lightweight component.s
+ *
+ * @param target the current mouse event target
+ * @param ev the mouse event
+ */
+ private void trackEnterExit(final Component target, final MouseEvent ev)
+ {
+ int id = ev.getID();
+ if (target != lastTarget)
+ {
+ if (lastTarget != null)
+ redispatch(ev, lastTarget, MouseEvent.MOUSE_EXITED);
+ if (id == MouseEvent.MOUSE_EXITED)
+ ev.consume();
+ if (target != null)
+ redispatch(ev, target, MouseEvent.MOUSE_ENTERED);
+ if (id == MouseEvent.MOUSE_ENTERED)
+ ev.consume();
+ lastTarget = target;
+ }
+
+ }
+
/**
- * Converts a point in the parent's coordinate system to a child coordinate
- * system. The resulting point is stored in the same Point object and
- * returned.
+ * Redispatches the specified mouse event to the specified target with the
+ * specified id.
*
- * @param parent the parent component
- * @param p the point
- * @param child the child component
+ * @param ev the mouse event
+ * @param target the new target
+ * @param id the new id
+ */
+ private void redispatch(MouseEvent ev, Component target, int id)
+ {
+ Component source = ev.getComponent();
+ if (target != null)
+ {
+ // Translate coordinates.
+ int x = ev.getX();
+ int y = ev.getY();
+ for (Component c = target; c != null && c != source; c = c.getParent())
+ {
+ x -= c.x;
+ y -= c.y;
+ }
+
+ // Retarget event.
+ MouseEvent retargeted;
+ if (id == MouseEvent.MOUSE_WHEEL)
+ {
+ MouseWheelEvent mwe = (MouseWheelEvent) ev;
+ retargeted = new MouseWheelEvent(target, id, ev.getWhen(),
+ ev.getModifiers()
+ | ev.getModifiersEx(), x, y,
+ ev.getClickCount(),
+ ev.isPopupTrigger(),
+ mwe.getScrollType(),
+ mwe.getScrollAmount(),
+ mwe.getWheelRotation());
+ }
+ else
+ {
+ retargeted = new MouseEvent(target, id, ev.getWhen(),
+ ev.getModifiers() | ev.getModifiersEx(),
+ x, y, ev.getClickCount(),
+ ev.isPopupTrigger());
+ }
+
+ if (target == source)
+ ((Container) target).dispatchNoLightweight(retargeted);
+ else
+ target.dispatchEvent(retargeted);
+ }
+ }
+
+ /**
+ * Determines if we are in the middle of a drag operation, that is, if
+ * any of the buttons is held down.
+ *
+ * @param ev the mouse event to check
*
- * @return the translated point
+ * @return <code>true</code> if we are in the middle of a drag operation,
+ * <code>false</code> otherwise
*/
- private Point convertPointToChild(Component parent, Point p,
- Component child)
+ private boolean isDragging(MouseEvent ev)
{
- int offX = 0;
- int offY = 0;
- Component comp = child;
- while (comp != null && comp != parent)
+ int mods = ev.getModifiersEx();
+ int id = ev.getID();
+ if (id == MouseEvent.MOUSE_PRESSED || id == MouseEvent.MOUSE_RELEASED)
{
- offX += comp.getX();
- offY += comp.getY();
- comp = comp.getParent();
+ switch (ev.getButton())
+ {
+ case MouseEvent.BUTTON1:
+ mods ^= InputEvent.BUTTON1_DOWN_MASK;
+ break;
+ case MouseEvent.BUTTON2:
+ mods ^= InputEvent.BUTTON2_DOWN_MASK;
+ break;
+ case MouseEvent.BUTTON3:
+ mods ^= InputEvent.BUTTON3_DOWN_MASK;
+ break;
+ }
}
- p.x -= offX;
- p.y -= offY;
- return p;
+ return (mods & (InputEvent.BUTTON1_DOWN_MASK
+ | InputEvent.BUTTON2_DOWN_MASK
+ | InputEvent.BUTTON3_DOWN_MASK)) != 0;
}
}