001 /**
002 * Copyright (c) 2009, Piet Blok
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions
007 * are met:
008 *
009 * * Redistributions of source code must retain the above copyright
010 * notice, this list of conditions and the following disclaimer.
011 * * Redistributions in binary form must reproduce the above
012 * copyright notice, this list of conditions and the following
013 * disclaimer in the documentation and/or other materials provided
014 * with the distribution.
015 * * Neither the name of the copyright holder nor the names of the
016 * contributors may be used to endorse or promote products derived
017 * from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
020 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
021 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
022 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
023 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
024 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
025 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
026 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
027 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
029 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031
032 package org.pbjar.jxlayer.plaf.ext;
033
034 import java.awt.AWTEvent;
035 import java.awt.Component;
036 import java.awt.Container;
037 import java.awt.Point;
038 import java.awt.Rectangle;
039 import java.awt.event.MouseEvent;
040 import java.awt.event.MouseWheelEvent;
041 import java.awt.event.MouseWheelListener;
042 import java.awt.geom.AffineTransform;
043 import java.awt.geom.NoninvertibleTransformException;
044
045 import javax.swing.JComponent;
046 import javax.swing.SwingUtilities;
047
048 import org.jdesktop.jxlayer.JXLayer;
049 import org.jdesktop.jxlayer.plaf.AbstractLayerUI;
050 import org.jdesktop.jxlayer.plaf.LayerUI;
051
052 /**
053 * This class provides for {@link MouseEvent} re-dispatching. It may be used to
054 * set a tool tip on {@link JXLayer}'s glass pane and still have the child
055 * components receive {@link MouseEvent}s.
056 * <p>
057 * <b>Note:</b> A {@link MouseEventUI} instance cannot be shared and can be set
058 * to a single {@link JXLayer} instance only.
059 * <p/>
060 */
061 public class MouseEventUI<V extends JComponent> extends AbstractLayerUI<V> {
062
063 private Component lastEnteredTarget, lastPressedTarget;
064
065 private boolean dispatchingMode = false;
066
067 private JXLayer<V> installedLayer;
068
069 /**
070 * Overridden to allow for re-dispatching of mouse events to their intended
071 * (visual) recipients, rather than to the components according to their
072 * bounds.
073 */
074 @Override
075 public void eventDispatched(AWTEvent event, final JXLayer<V> layer) {
076
077 if (event instanceof MouseEvent) {
078 if (!dispatchingMode) {
079 dispatchingMode = true;
080 try {
081 redispatch((MouseEvent) event, layer);
082 } finally {
083 dispatchingMode = false;
084 }
085 } else {
086 Component component = ((MouseEvent) event).getComponent();
087 layer.getGlassPane().setCursor(component.getCursor());
088 }
089 }
090 }
091
092 /**
093 * Overridden to only get the following event types:
094 * {@link AWTEvent#MOUSE_EVENT_MASK},
095 * {@link AWTEvent#MOUSE_MOTION_EVENT_MASK} and
096 * {@link AWTEvent#MOUSE_WHEEL_EVENT_MASK}.
097 *
098 */
099 @Override
100 public long getLayerEventMask() {
101 return AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK
102 | AWTEvent.MOUSE_WHEEL_EVENT_MASK;
103 }
104
105 /**
106 * Overridden to check if this {@link LayerUI} has not been installed
107 * already, and to set the argument {@code component} as the installed
108 * {@link JXLayer}.
109 *
110 * @throws IllegalStateException
111 * when this {@link LayerUI} has been installed already
112 * @see #getInstalledLayer()
113 */
114 @SuppressWarnings("unchecked")
115 @Override
116 public void installUI(JComponent component) throws IllegalStateException {
117 super.installUI(component);
118 if (installedLayer != null) {
119 throw new IllegalStateException(this.getClass().getName()
120 + " cannot be shared between multiple layers");
121 }
122 installedLayer = (JXLayer<V>) component;
123 }
124
125 /**
126 * Overridden to remove the installed {@link JXLayer}.
127 */
128 @Override
129 public void uninstallUI(JComponent c) {
130 installedLayer = null;
131 super.uninstallUI(c);
132 }
133
134 private Point calculateTargetPoint(JXLayer<V> layer, MouseEvent mouseEvent) {
135 Point point = mouseEvent.getPoint();
136 Rectangle layerBounds = layer.getBounds();
137 Container parent = layer.getParent();
138 Rectangle parentRectangle = new Rectangle(-layerBounds.x,
139 -layerBounds.y, parent.getWidth(), parent.getHeight());
140 SwingUtilities.convertPointToScreen(point, mouseEvent.getComponent());
141 SwingUtilities.convertPointFromScreen(point, layer);
142 if (parentRectangle.contains(point)) {
143 return transformPoint(layer, point);
144 } else {
145 return new Point(-1, -1);
146 }
147 }
148
149 private MouseWheelEvent createMouseWheelEvent(
150 MouseWheelEvent mouseWheelEvent, Point point, Component target) {
151 return new MouseWheelEvent(target, //
152 mouseWheelEvent.getID(), //
153 mouseWheelEvent.getWhen(), //
154 mouseWheelEvent.getModifiers(), //
155 point.x, //
156 point.y, //
157 mouseWheelEvent.getClickCount(), //
158 mouseWheelEvent.isPopupTrigger(), //
159 mouseWheelEvent.getScrollType(), //
160 mouseWheelEvent.getScrollAmount(), //
161 mouseWheelEvent.getWheelRotation() //
162 );
163 }
164
165 private void dispatchMouseEvent(MouseEvent mouseEvent) {
166 if (mouseEvent != null) {
167 Component target = mouseEvent.getComponent();
168 target.dispatchEvent(mouseEvent);
169 }
170 }
171
172 private Component findWheelListenerComponent(Component target) {
173 if (target == null) {
174 return null;
175 } else if (target.getMouseWheelListeners().length == 0) {
176 return findWheelListenerComponent(target.getParent());
177 } else {
178 return target;
179 }
180 }
181
182 private void generateEnterExitEvents(JXLayer<V> layer,
183 MouseEvent originalEvent, Component newTarget, Point realPoint) {
184 if (lastEnteredTarget != newTarget) {
185 dispatchMouseEvent(transformMouseEvent(layer, originalEvent,
186 lastEnteredTarget, realPoint, MouseEvent.MOUSE_EXITED));
187 lastEnteredTarget = newTarget;
188 dispatchMouseEvent(transformMouseEvent(layer, originalEvent,
189 lastEnteredTarget, realPoint, MouseEvent.MOUSE_ENTERED));
190 }
191 }
192
193 private Component getListeningComponent(MouseEvent event,
194 Component component) {
195 switch (event.getID()) {
196 case (MouseEvent.MOUSE_CLICKED):
197 case (MouseEvent.MOUSE_ENTERED):
198 case (MouseEvent.MOUSE_EXITED):
199 case (MouseEvent.MOUSE_PRESSED):
200 case (MouseEvent.MOUSE_RELEASED):
201 return getMouseListeningComponent(component);
202 case (MouseEvent.MOUSE_DRAGGED):
203 case (MouseEvent.MOUSE_MOVED):
204 return getMouseMotionListeningComponent(component);
205 case (MouseEvent.MOUSE_WHEEL):
206 return getMouseWheelListeningComponent(component);
207 }
208 return null;
209 }
210
211 private Component getMouseListeningComponent(Component component) {
212 if (component.getMouseListeners().length > 0) {
213 return component;
214 } else {
215 Container parent = component.getParent();
216 if (parent != null) {
217 return getMouseListeningComponent(parent);
218 } else {
219 return null;
220 }
221 }
222 }
223
224 private Component getMouseMotionListeningComponent(Component component) {
225 /*
226 * Mouse motion events may result in MOUSE_ENTERED and MOUSE_EXITED.
227 *
228 * Therefore, components with MouseListeners registered should be
229 * returned as well.
230 */
231 if (component.getMouseMotionListeners().length > 0
232 || component.getMouseListeners().length > 0) {
233 return component;
234 } else {
235 Container parent = component.getParent();
236 if (parent != null) {
237 return getMouseMotionListeningComponent(parent);
238 } else {
239 return null;
240 }
241 }
242 }
243
244 private Component getMouseWheelListeningComponent(Component component) {
245 if (component.getMouseWheelListeners().length > 0) {
246 return component;
247 } else {
248 Container parent = component.getParent();
249 if (parent != null) {
250 return getMouseWheelListeningComponent(parent);
251 } else {
252 return null;
253 }
254 }
255 }
256
257 private Component getTarget(JXLayer<V> layer, Point targetPoint) {
258 Component view = layer.getView();
259 if (view == null) {
260 return null;
261 } else {
262 Point viewPoint = SwingUtilities.convertPoint(layer, targetPoint,
263 view);
264 return SwingUtilities.getDeepestComponentAt(view, viewPoint.x,
265 viewPoint.y);
266 }
267 }
268
269 private void redispatch(MouseEvent originalEvent, final JXLayer<V> layer) {
270 if (layer.getView() != null) {
271 if (originalEvent.getComponent() != layer.getGlassPane()) {
272 originalEvent.consume();
273 }
274 MouseEvent newEvent = null;
275
276 Point realPoint = calculateTargetPoint(layer, originalEvent);
277 Component realTarget = getTarget(layer, realPoint);
278 if (realTarget != null) {
279 realTarget = getListeningComponent(originalEvent, realTarget);
280 }
281
282 switch (originalEvent.getID()) {
283 case MouseEvent.MOUSE_PRESSED:
284 newEvent = transformMouseEvent(layer, originalEvent,
285 realTarget, realPoint);
286 if (newEvent != null) {
287 lastPressedTarget = newEvent.getComponent();
288 }
289 break;
290 case MouseEvent.MOUSE_RELEASED:
291 newEvent = transformMouseEvent(layer, originalEvent,
292 lastPressedTarget, realPoint);
293 lastPressedTarget = null;
294 break;
295 case MouseEvent.MOUSE_ENTERED:
296 generateEnterExitEvents(layer, originalEvent, realTarget,
297 realPoint);
298 break;
299 case MouseEvent.MOUSE_EXITED:
300 generateEnterExitEvents(layer, originalEvent, realTarget,
301 realPoint);
302 break;
303 case MouseEvent.MOUSE_MOVED:
304 newEvent = transformMouseEvent(layer, originalEvent,
305 realTarget, realPoint);
306 generateEnterExitEvents(layer, originalEvent, realTarget,
307 realPoint);
308 break;
309 case MouseEvent.MOUSE_DRAGGED:
310 newEvent = transformMouseEvent(layer, originalEvent,
311 lastPressedTarget, realPoint);
312 generateEnterExitEvents(layer, originalEvent, realTarget,
313 realPoint);
314 break;
315 case MouseEvent.MOUSE_CLICKED:
316 newEvent = transformMouseEvent(layer, originalEvent,
317 realTarget, realPoint);
318 break;
319 case (MouseEvent.MOUSE_WHEEL):
320 redispatchMouseWheelEvent((MouseWheelEvent) originalEvent,
321 realTarget, layer);
322 break;
323 }
324 dispatchMouseEvent(newEvent);
325 }
326 }
327
328 private void redispatchMouseWheelEvent(MouseWheelEvent mouseWheelEvent,
329 Component target, JXLayer<V> layer) {
330 MouseWheelEvent newEvent = this.transformMouseWheelEvent(
331 mouseWheelEvent, target, layer);
332 processMouseWheelEvent(newEvent, layer);
333 }
334
335 private MouseEvent transformMouseEvent(JXLayer<V> layer,
336 MouseEvent mouseEvent, Component target, Point realPoint) {
337 return transformMouseEvent(layer, mouseEvent, target, realPoint,
338 mouseEvent.getID());
339 }
340
341 private MouseEvent transformMouseEvent(JXLayer<V> layer,
342 MouseEvent mouseEvent, Component target, Point targetPoint, int id) {
343 if (target == null) {
344 return null;
345 } else {
346 Point newPoint = new Point(targetPoint);
347 SwingUtilities.convertPointToScreen(newPoint, layer);
348 SwingUtilities.convertPointFromScreen(newPoint, target);
349 return new MouseEvent(target, //
350 id, //
351 mouseEvent.getWhen(), //
352 mouseEvent.getModifiers(), //
353 newPoint.x, //
354 newPoint.y, //
355 mouseEvent.getClickCount(), //
356 mouseEvent.isPopupTrigger(), //
357 mouseEvent.getButton());
358 }
359 }
360
361 private MouseWheelEvent transformMouseWheelEvent(
362 MouseWheelEvent mouseWheelEvent, Component target, JXLayer<V> layer) {
363 if (target == null) {
364 target = layer;
365 }
366 Point point = SwingUtilities.convertPoint(mouseWheelEvent
367 .getComponent(), mouseWheelEvent.getPoint(), target);
368 MouseWheelEvent newEvent = createMouseWheelEvent(mouseWheelEvent,
369 point, target);
370 return newEvent;
371 }
372
373 private Point transformPoint(JXLayer<V> layer, Point point) {
374 AffineTransform transform = this.getTransform(layer);
375 if (transform != null) {
376 try {
377 transform.inverseTransform(point, point);
378 } catch (NoninvertibleTransformException e) {
379 e.printStackTrace();
380 }
381 }
382 return point;
383 }
384
385 protected JXLayer<V> getInstalledLayer() {
386 return installedLayer;
387 }
388
389 /**
390 * Re-dispatches the event to the first component in the hierarchy that has
391 * a {@link MouseWheelListener} registered.
392 */
393 @Override
394 protected void processMouseWheelEvent(MouseWheelEvent event,
395 JXLayer<V> jxlayer) {
396 /*
397 * Only process an event if it is not already consumed. This may be the
398 * case if this LayerUI is contained in a wrapped hierarchy.
399 */
400 if (!event.isConsumed()) {
401 /*
402 * Since we will create a new event, the argument event must be
403 * consumed.
404 */
405 event.consume();
406 /*
407 * Find a target up in the hierarchy that has
408 * MouseWheelEventListeners registered.
409 */
410 Component target = event.getComponent();
411 Component newTarget = findWheelListenerComponent(target);
412 if (newTarget == null) {
413 newTarget = jxlayer.getParent();
414 }
415 /*
416 * Convert the location relative to the new target
417 */
418 Point point = SwingUtilities.convertPoint(event.getComponent(),
419 event.getPoint(), newTarget);
420 /*
421 * Create a new event and dispatch it.
422 */
423 newTarget.dispatchEvent(createMouseWheelEvent(event, point,
424 newTarget));
425 }
426 }
427
428 }