| /* 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; |
| } |
| } |