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