| /* LightweightDispatcher.java -- Dispatches mouse events to lightweights | |
| Copyright (C) 2006 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| 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 (kennke@aicas.com) | |
| */ | |
| 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 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); | |
| } | |
| return instance; | |
| } | |
| /** | |
| * Creates a new LightweightDispatcher. This is private to prevent access | |
| * from outside. Use {@link #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(final AWTEvent event) | |
| { | |
| 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(final MouseEvent ev) | |
| { | |
| 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) | |
| { | |
| switch (id) | |
| { | |
| case MouseEvent.MOUSE_ENTERED: | |
| case MouseEvent.MOUSE_EXITED: | |
| // This is already handled in trackEnterExit(). | |
| break; | |
| case MouseEvent.MOUSE_PRESSED: | |
| case MouseEvent.MOUSE_RELEASED: | |
| case MouseEvent.MOUSE_MOVED: | |
| redispatch(ev, mouseEventTarget, id); | |
| break; | |
| case MouseEvent.MOUSE_CLICKED: | |
| // MOUSE_CLICKED must be dispatched to the original target of | |
| // MOUSE_PRESSED. | |
| if (target == mouseEventTarget) | |
| redispatch(ev, mouseEventTarget, id); | |
| break; | |
| case MouseEvent.MOUSE_DRAGGED: | |
| if (isDragging(ev)) | |
| redispatch(ev, mouseEventTarget, id); | |
| break; | |
| case MouseEvent.MOUSE_WHEEL: | |
| redispatch(ev, mouseEventTarget, id); | |
| } | |
| ev.consume(); | |
| } | |
| 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(final Container c, final int x, final int y) | |
| { | |
| Component target = 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++) | |
| { | |
| 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)) | |
| { | |
| // Check if there's a deeper possible target. | |
| if (child instanceof Container) | |
| { | |
| 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; | |
| } | |
| } | |
| /** | |
| * Redispatches the specified mouse event to the specified target with the | |
| * specified id. | |
| * | |
| * @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(); | |
| assert target != null; | |
| if (target.isShowing()) | |
| { | |
| // 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(), ev.getButton()); | |
| } | |
| 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 <code>true</code> if we are in the middle of a drag operation, | |
| * <code>false</code> otherwise | |
| */ | |
| private boolean isDragging(MouseEvent ev) | |
| { | |
| int mods = ev.getModifiersEx(); | |
| int id = ev.getID(); | |
| if (id == MouseEvent.MOUSE_PRESSED || id == MouseEvent.MOUSE_RELEASED) | |
| { | |
| 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; | |
| } | |
| } | |
| return (mods & (InputEvent.BUTTON1_DOWN_MASK | |
| | InputEvent.BUTTON2_DOWN_MASK | |
| | InputEvent.BUTTON3_DOWN_MASK)) != 0; | |
| } | |
| } |