1 /* LightweightDispatcher.java -- Dispatches mouse events to lightweights
2    Copyright (C) 2006 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.awt;
40 
41 import java.awt.event.InputEvent;
42 import java.awt.event.MouseEvent;
43 import java.awt.event.MouseWheelEvent;
44 import java.awt.peer.LightweightPeer;
45 import java.util.WeakHashMap;
46 
47 /**
48  * Redispatches mouse events to lightweight components. The native peers know
49  * nothing about the lightweight components and thus mouse events are always
50  * targetted at Windows or heavyweight components. This class listenes directly
51  * on the eventqueue and dispatches mouse events to lightweight components.
52  *
53  * @author Roman Kennke (kennke@aicas.com)
54  */
55 final class LightweightDispatcher
56 {
57 
58   /**
59    * Maps thread groups to lightweight dispatcher instances. We need to
60    * have one instance per thread group so that 2 or more applets or otherwise
61    * separated applications (like in OSGI) do not interfer with each other.
62    */
63   private static WeakHashMap instances = new WeakHashMap();
64 
65   /**
66    * The last mouse event target. If the target changes, additional
67    * MOUSE_ENTERED and MOUSE_EXITED events must be dispatched.
68    */
69   private Component lastTarget;
70 
71   /**
72    * The current mouseEventTarget.
73    */
74   private Component mouseEventTarget;
75 
76   /**
77    * Returns an instance of LightweightDispatcher for the current thread's
78    * thread group.
79    *
80    * @return an instance of LightweightDispatcher for the current thread's
81    *         thread group
82    */
getInstance()83   static LightweightDispatcher getInstance()
84   {
85     Thread t = Thread.currentThread();
86     ThreadGroup tg = t.getThreadGroup();
87     LightweightDispatcher instance = (LightweightDispatcher) instances.get(tg);
88     if (instance == null)
89       {
90         instance = new LightweightDispatcher();
91         instances.put(tg, instance);
92       }
93     return instance;
94   }
95 
96   /**
97    * Creates a new LightweightDispatcher. This is private to prevent access
98    * from outside. Use {@link #getInstance()} instead.
99    */
LightweightDispatcher()100   private LightweightDispatcher()
101   {
102     // Nothing to do here.
103   }
104 
105   /**
106    * Receives notification if a mouse event passes along the eventqueue.
107    *
108    * @param event the event
109    */
dispatchEvent(final AWTEvent event)110   public boolean dispatchEvent(final AWTEvent event)
111   {
112     if (event instanceof MouseEvent)
113       {
114         MouseEvent mouseEvent = (MouseEvent) event;
115         return handleMouseEvent(mouseEvent);
116       }
117     return false;
118   }
119 
120   /**
121    * Handles all mouse events that are targetted at toplevel containers
122    * (Window instances) and dispatches them to the correct lightweight child.
123    *
124    * @param ev the mouse event
125    * @return whether or not we found a lightweight that handled the event.
126    */
handleMouseEvent(final MouseEvent ev)127   private boolean handleMouseEvent(final MouseEvent ev)
128   {
129     Container container = (Container) ev.getSource();
130     Component target = findTarget(container, ev.getX(), ev.getY());
131     trackEnterExit(target, ev);
132     int id = ev.getID();
133 
134     // Dont update the mouseEventTarget when dragging. Also, MOUSE_CLICKED
135     // must be dispatched to the original target of MOUSE_PRESSED, so don't
136     // update in this case either.
137     if (! isDragging(ev) && id != MouseEvent.MOUSE_CLICKED)
138       mouseEventTarget = (target != container) ? target : null;
139 
140     if (mouseEventTarget != null)
141       {
142         switch (id)
143           {
144           case MouseEvent.MOUSE_ENTERED:
145           case MouseEvent.MOUSE_EXITED:
146             // This is already handled in trackEnterExit().
147             break;
148           case MouseEvent.MOUSE_PRESSED:
149           case MouseEvent.MOUSE_RELEASED:
150           case MouseEvent.MOUSE_MOVED:
151             redispatch(ev, mouseEventTarget, id);
152             break;
153           case MouseEvent.MOUSE_CLICKED:
154             // MOUSE_CLICKED must be dispatched to the original target of
155             // MOUSE_PRESSED.
156             if (target == mouseEventTarget)
157               redispatch(ev, mouseEventTarget, id);
158             break;
159           case MouseEvent.MOUSE_DRAGGED:
160             if (isDragging(ev))
161               redispatch(ev, mouseEventTarget, id);
162             break;
163           case MouseEvent.MOUSE_WHEEL:
164             redispatch(ev, mouseEventTarget, id);
165           }
166         ev.consume();
167       }
168 
169     return ev.isConsumed();
170   }
171 
172   /**
173    * Finds the actual target for a mouseevent, starting at <code>c</code>.
174    * This searches through the children of the container and finds the first
175    * one which is showing, at the location from the mouse event and has
176    * a MouseListener or MouseMotionListener attached. If no such child component
177    * is found, null is returned.
178    *
179    * @param c the container to search through
180    * @param loc the mouse event point
181    *
182    * @return the actual receiver of the mouse event, or null, if no such
183    *         component has been found
184    */
findTarget(final Container c, final int x, final int y)185   private Component findTarget(final Container c, final int x, final int y)
186   {
187     Component target = null;
188 
189     // First we check the children of the container.
190 
191     // Note: It is important that we use the package private Container
192     // fields ncomponents and component here. There are applications
193     // that override getComponentCount()
194     // and getComponent() to hide internal components, which makes
195     // the LightweightDispatcher not work correctly in these cases.
196     // As a positive sideeffect this is slightly more efficient.
197     int nChildren = c.ncomponents;
198     for (int i = 0; i < nChildren && target == null; i++)
199       {
200         Component child = c.component[i];
201         int childX = x - child.x;
202         int childY = y - child.y;
203         if (child != null && child.visible
204             && child.peer instanceof LightweightPeer
205             && child.contains(childX, childY))
206           {
207             // Check if there's a deeper possible target.
208             if (child instanceof Container)
209               {
210                 Component deeper = findTarget((Container) child,
211                                               childX, childY);
212                 if (deeper != null)
213                   target = deeper;
214               }
215             // Check if the child itself is interested in mouse events.
216             else if (isMouseListening(child))
217               target = child;
218           }
219       }
220 
221     // Check the container itself, if we didn't find a target yet.
222     if (target == null  && c.contains(x, y) && isMouseListening(c))
223       target = c;
224 
225     return target;
226   }
227 
228   /**
229    * Checks if the specified component would be interested in a mouse event.
230    *
231    * @param c the component to check
232    *
233    * @return <code>true</code> if the component has mouse listeners installed,
234    *         <code>false</code> otherwise
235    */
isMouseListening(final Component c)236   private boolean isMouseListening(final Component c)
237   {
238     // Note: It is important to NOT check if the component is listening
239     // for a specific event (for instance, mouse motion events). The event
240     // gets dispatched to the component if the component is listening
241     // for ANY mouse event, even when the component is not listening for the
242     // specific type of event. There are applications that depend on this
243     // (sadly).
244     return c.mouseListener != null
245            || c.mouseMotionListener != null
246            || c.mouseWheelListener != null
247            || (c.eventMask & AWTEvent.MOUSE_EVENT_MASK) != 0
248            || (c.eventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0
249            || (c.eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0;
250   }
251 
252   /**
253    * Tracks MOUSE_ENTERED and MOUSE_EXIT as well as MOUSE_MOVED and
254    * MOUSE_DRAGGED and creates synthetic MOUSE_ENTERED and MOUSE_EXITED for
255    * lightweight component.s
256    *
257    * @param target the current mouse event target
258    * @param ev the mouse event
259    */
trackEnterExit(final Component target, final MouseEvent ev)260   private void trackEnterExit(final Component target, final MouseEvent ev)
261   {
262     int id = ev.getID();
263     if (target != lastTarget)
264       {
265         if (lastTarget != null)
266           redispatch(ev, lastTarget, MouseEvent.MOUSE_EXITED);
267         if (id == MouseEvent.MOUSE_EXITED)
268           ev.consume();
269         if (target != null)
270           redispatch(ev, target, MouseEvent.MOUSE_ENTERED);
271         if (id == MouseEvent.MOUSE_ENTERED)
272           ev.consume();
273         lastTarget = target;
274       }
275 
276   }
277 
278   /**
279    * Redispatches the specified mouse event to the specified target with the
280    * specified id.
281    *
282    * @param ev the mouse event
283    * @param target the new target
284    * @param id the new id
285    */
redispatch(MouseEvent ev, Component target, int id)286   private void redispatch(MouseEvent ev, Component target, int id)
287   {
288     Component source = ev.getComponent();
289     assert target != null;
290     if (target.isShowing())
291       {
292         // Translate coordinates.
293         int x = ev.getX();
294         int y = ev.getY();
295         for (Component c = target; c != null && c != source; c = c.getParent())
296           {
297             x -= c.x;
298             y -= c.y;
299           }
300 
301         // Retarget event.
302         MouseEvent retargeted;
303         if (id == MouseEvent.MOUSE_WHEEL)
304           {
305             MouseWheelEvent mwe = (MouseWheelEvent) ev;
306             retargeted = new MouseWheelEvent(target, id, ev.getWhen(),
307                                              ev.getModifiers()
308                                              | ev.getModifiersEx(), x, y,
309                                              ev.getClickCount(),
310                                              ev.isPopupTrigger(),
311                                              mwe.getScrollType(),
312                                              mwe.getScrollAmount(),
313                                              mwe.getWheelRotation());
314           }
315         else
316           {
317             retargeted = new MouseEvent(target, id, ev.getWhen(),
318                                        ev.getModifiers() | ev.getModifiersEx(),
319                                        x, y, ev.getClickCount(),
320                                        ev.isPopupTrigger(), ev.getButton());
321           }
322 
323         if (target == source)
324           ((Container) target).dispatchNoLightweight(retargeted);
325         else
326           target.dispatchEvent(retargeted);
327       }
328   }
329 
330   /**
331    * Determines if we are in the middle of a drag operation, that is, if
332    * any of the buttons is held down.
333    *
334    * @param ev the mouse event to check
335    *
336    * @return <code>true</code> if we are in the middle of a drag operation,
337    *         <code>false</code> otherwise
338    */
isDragging(MouseEvent ev)339   private boolean isDragging(MouseEvent ev)
340   {
341     int mods = ev.getModifiersEx();
342     int id = ev.getID();
343     if (id == MouseEvent.MOUSE_PRESSED || id == MouseEvent.MOUSE_RELEASED)
344       {
345         switch (ev.getButton())
346           {
347             case MouseEvent.BUTTON1:
348               mods ^= InputEvent.BUTTON1_DOWN_MASK;
349               break;
350             case MouseEvent.BUTTON2:
351               mods ^= InputEvent.BUTTON2_DOWN_MASK;
352               break;
353             case MouseEvent.BUTTON3:
354               mods ^= InputEvent.BUTTON3_DOWN_MASK;
355               break;
356           }
357       }
358     return (mods & (InputEvent.BUTTON1_DOWN_MASK
359                     | InputEvent.BUTTON2_DOWN_MASK
360                     | InputEvent.BUTTON3_DOWN_MASK)) != 0;
361   }
362 }
363