| /* 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.MouseEvent; | |
| 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) | |
| */ | |
| 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; | |
| /** | |
| * 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(AWTEvent event) | |
| { | |
| if (event instanceof MouseEvent && event.getSource() instanceof Window) | |
| { | |
| 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) | |
| { | |
| 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 = null; | |
| if (parent != null) | |
| { | |
| target = findTarget(parent, loc); | |
| while (target == null && parent != null) | |
| { | |
| if (parent.mouseListener != null | |
| || parent.mouseMotionListener != null | |
| || (parent.eventMask | |
| & (AWTEvent.MOUSE_EVENT_MASK | |
| | AWTEvent.MOUSE_MOTION_EVENT_MASK)) != 0) | |
| { | |
| target = parent; | |
| } | |
| else | |
| parent = parent.getParent(); | |
| } | |
| } | |
| if (target == null || target.isLightweight()) | |
| { | |
| // Dispatch additional MOUSE_EXITED and MOUSE_ENTERED if event target | |
| // is different from the last event target. | |
| if (target != lastTarget) | |
| { | |
| 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()); | |
| //System.err.println("event: " + mouseExited); | |
| 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()); | |
| //System.err.println("event: " + mouseEntered); | |
| 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; | |
| break; | |
| 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; | |
| 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; | |
| 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; | |
| 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; | |
| } | |
| else | |
| return false; | |
| } | |
| /** | |
| * 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 numComponents = c.getComponentCount(); | |
| Component target = null; | |
| if (c != null) | |
| { | |
| for (int i = 0; i < numComponents; i++) | |
| { | |
| Component child = c.getComponent(i); | |
| if (child.isShowing()) | |
| { | |
| if (child.contains(loc.x - child.getX(), loc.y - child.getY()) | |
| && (child.mouseListener != null | |
| || child.mouseMotionListener != null | |
| || (child.eventMask | |
| & (AWTEvent.MOUSE_EVENT_MASK | |
| | AWTEvent.MOUSE_MOTION_EVENT_MASK)) != 0)) | |
| { | |
| target = child; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return 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. | |
| * | |
| * @param parent the parent component | |
| * @param p the point | |
| * @param child the child component | |
| * | |
| * @return the translated point | |
| */ | |
| private Point convertPointToChild(Component parent, Point p, | |
| Component child) | |
| { | |
| int offX = 0; | |
| int offY = 0; | |
| Component comp = child; | |
| while (comp != null && comp != parent) | |
| { | |
| offX += comp.getX(); | |
| offY += comp.getY(); | |
| comp = comp.getParent(); | |
| } | |
| p.x -= offX; | |
| p.y -= offY; | |
| return p; | |
| } | |
| } |