001 /**
002 * Copyright (c) 2008-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.misc;
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.Toolkit;
040 import java.awt.event.AWTEventListener;
041 import java.awt.event.MouseEvent;
042 import java.awt.event.MouseWheelEvent;
043
044 import javax.swing.JComponent;
045 import javax.swing.SwingUtilities;
046
047 import org.jdesktop.jxlayer.JXLayer;
048
049 /**
050 * Manages mouse events for {@link JXLayer}. Mouse events targeted at the glass
051 * pane are dispatched to the glass pane itself and to the children in the
052 * component hierarchy. Other mouse events are dispatched only to the children.
053 * <p>
054 * Sub classes may apply transformations in order to select a different child
055 * component than the component associated with the event..
056 * </p>
057 *
058 * <p>
059 * The {@code AbstractTransparentMouseUI} class is <b>not</b> designed for
060 * shared use, but shared use <em>may</em> work. I just didn't test it.
061 * </p>
062 *
063 * @author Piet Blok
064 *
065 * @param <V>
066 * JXLayer's view
067 * @param <S>
068 * A state object
069 */
070 public abstract class AbstractTransparentMouseUI<V extends JComponent, S> extends
071 GeneralLayerUI<V, S> {
072
073 private Component lastEnteredTarget, lastPressedTarget;
074
075 private boolean dispatchingMode = false;
076
077 /**
078 * Construct a new {@code UI} with {@link AWTEventListener} disabled.
079 */
080 public AbstractTransparentMouseUI() {
081 this(false);
082 }
083
084 /**
085 * Construct a new {@code UI}.
086 *
087 * @param enableAWTEventListener
088 * if {@code true}, {@link AWTEventListener} will be enabled.
089 * Specifying {@code true} solves a problem with non editable
090 * {@code JTextComponent}s.
091 * @throws SecurityException
092 * when enableAWTEventListener is {@code true} and if a security
093 * manager exists and its {@code checkPermission} method doesn't
094 * allow the operation.
095 * @see Toolkit#addAWTEventListener(AWTEventListener, long)
096 */
097 public AbstractTransparentMouseUI(boolean enableAWTEventListener) {
098 super(enableAWTEventListener);
099 }
100
101 /**
102 * Overridden to allow for re dispatching of mouse events to their intended
103 * (visual) recipients, rather than to the components according to their
104 * bounds.
105 */
106 @Override
107 public void eventDispatched(AWTEvent event, final JXLayer<V> layer) {
108
109 if (event instanceof MouseEvent) {
110 if (!dispatchingMode) {
111 dispatchingMode = true;
112 try {
113 redispatch((MouseEvent) event, layer);
114 } finally {
115 dispatchingMode = false;
116 }
117 } else {
118 Component component = ((MouseEvent) event).getComponent();
119 layer.getGlassPane().setCursor(component.getCursor());
120 }
121 }
122 }
123
124 /**
125 * Overridden to only get the following event types:
126 * {@link AWTEvent#MOUSE_EVENT_MASK},
127 * {@link AWTEvent#MOUSE_MOTION_EVENT_MASK} and
128 * {@link AWTEvent#MOUSE_WHEEL_EVENT_MASK}.
129 *
130 */
131 @Override
132 public long getLayerEventMask() {
133 return AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK
134 | AWTEvent.MOUSE_WHEEL_EVENT_MASK;
135 }
136
137 private Point calculateTargetPoint(JXLayer<V> layer, MouseEvent mouseEvent) {
138 Point point = mouseEvent.getPoint();
139 V view = layer.getView();
140 Rectangle layerBounds = layer.getBounds();
141 Container parent = layer.getParent();
142 Rectangle parentRectangle = new Rectangle(-layerBounds.x,
143 -layerBounds.y, parent.getWidth(), parent.getHeight());
144 SwingUtilities.convertPointToScreen(point, mouseEvent.getComponent());
145 SwingUtilities.convertPointFromScreen(point, view);
146 if (parentRectangle.contains(point)) {
147 return transformPoint(layer, point);
148 } else {
149 // Return a point outside the layer.
150 return new Point(-1, -1);
151 }
152 }
153
154 private void dispatchMouseEvent(MouseEvent mouseEvent) {
155 if (mouseEvent != null) {
156 Component target = mouseEvent.getComponent();
157 target.dispatchEvent(mouseEvent);
158 }
159 }
160
161 private void generateEnterExitEvents(JXLayer<V> layer,
162 MouseEvent originalEvent, Component newTarget, Point realPoint) {
163 if (lastEnteredTarget != newTarget) {
164 dispatchMouseEvent(transformMouseEvent(layer, originalEvent,
165 lastEnteredTarget, realPoint, MouseEvent.MOUSE_EXITED));
166 lastEnteredTarget = newTarget;
167 dispatchMouseEvent(transformMouseEvent(layer, originalEvent,
168 lastEnteredTarget, realPoint, MouseEvent.MOUSE_ENTERED));
169 }
170 }
171
172 /**
173 * Find the first component up in the hierarchy that may be listening for a
174 * specific event.
175 *
176 * @param event
177 * the event
178 * @param component
179 * the first component to investigate
180 * @return A component that is listening for this type of event, or {@code
181 * null} if such a component can not be found.
182 */
183 private Component getListeningComponent(MouseEvent event,
184 Component component) {
185 switch (event.getID()) {
186 case (MouseEvent.MOUSE_CLICKED):
187 case (MouseEvent.MOUSE_ENTERED):
188 case (MouseEvent.MOUSE_EXITED):
189 case (MouseEvent.MOUSE_PRESSED):
190 case (MouseEvent.MOUSE_RELEASED):
191 return getMouseListeningComponent(component);
192 case (MouseEvent.MOUSE_DRAGGED):
193 case (MouseEvent.MOUSE_MOVED):
194 return getMouseMotionListeningComponent(component);
195 case (MouseEvent.MOUSE_WHEEL):
196 return getMouseWheelListeningComponent(component);
197 }
198 return null;
199 }
200
201 private Component getMouseListeningComponent(Component component) {
202 if (component.getMouseListeners().length > 0) {
203 return component;
204 } else {
205 Container parent = component.getParent();
206 if (parent != null) {
207 return getMouseListeningComponent(parent);
208 } else {
209 return null;
210 }
211 }
212 }
213
214 private Component getMouseMotionListeningComponent(Component component) {
215 /*
216 * Mouse motion events may result in MOUSE_ENTERED and MOUSE_EXITED.
217 *
218 * Therefore, components with MouseListeners registered should be
219 * returned as well.
220 */
221 if (component.getMouseMotionListeners().length > 0
222 || component.getMouseListeners().length > 0) {
223 return component;
224 } else {
225 Container parent = component.getParent();
226 if (parent != null) {
227 return getMouseMotionListeningComponent(parent);
228 } else {
229 return null;
230 }
231 }
232 }
233
234 private Component getMouseWheelListeningComponent(Component component) {
235 if (component.getMouseWheelListeners().length > 0) {
236 return component;
237 } else {
238 Container parent = component.getParent();
239 if (parent != null) {
240 return getMouseWheelListeningComponent(parent);
241 } else {
242 return null;
243 }
244 }
245 }
246
247 private Component getTarget(JXLayer<V> layer, Point targetPoint) {
248 Component userTopLevel = getTopLevelUserComponent(layer);
249 if (userTopLevel == null) {
250 return null;
251 } else {
252 Point viewPoint = SwingUtilities.convertPoint(layer.getView(),
253 targetPoint, userTopLevel);
254 return SwingUtilities.getDeepestComponentAt(userTopLevel,
255 viewPoint.x, viewPoint.y);
256 }
257 }
258
259 private void redispatch(MouseEvent originalEvent, final JXLayer<V> layer) {
260
261 V view = layer.getView();
262 if (view != null) {
263 if (originalEvent.getComponent() != layer.getGlassPane()) {
264 originalEvent.consume();
265 }
266 MouseEvent newEvent = null;
267
268 Point realPoint = calculateTargetPoint(layer, originalEvent);
269 Component realTarget = getTarget(layer, realPoint);
270 if (realTarget != null) {
271 realTarget = getListeningComponent(originalEvent, realTarget);
272 }
273
274 switch (originalEvent.getID()) {
275 case MouseEvent.MOUSE_RELEASED:
276 newEvent = transformMouseEvent(layer, originalEvent,
277 lastPressedTarget, realPoint);
278 lastPressedTarget = null;
279 break;
280 case MouseEvent.MOUSE_ENTERED:
281 // is ignored, see MOUSE_MOVED / MOUSE_DRAGGED
282 generateEnterExitEvents(layer, originalEvent, realTarget,
283 realPoint);
284 break;
285 case MouseEvent.MOUSE_EXITED:
286 // is ignored, see MOUSE_MOVED / MOUSE_DRAGGED
287 generateEnterExitEvents(layer, originalEvent, realTarget,
288 realPoint);
289 break;
290 case MouseEvent.MOUSE_CLICKED:
291 newEvent = transformMouseEvent(layer, originalEvent,
292 realTarget, realPoint);
293 break;
294 case MouseEvent.MOUSE_PRESSED:
295 newEvent = transformMouseEvent(layer, originalEvent,
296 realTarget, realPoint);
297 if (newEvent != null) {
298 lastPressedTarget = newEvent.getComponent();
299 }
300 break;
301 case MouseEvent.MOUSE_MOVED:
302 newEvent = transformMouseEvent(layer, originalEvent,
303 realTarget, realPoint);
304 generateEnterExitEvents(layer, originalEvent, realTarget,
305 realPoint);
306 break;
307 case MouseEvent.MOUSE_DRAGGED:
308 newEvent = transformMouseEvent(layer, originalEvent,
309 lastPressedTarget, realPoint);
310 generateEnterExitEvents(layer, originalEvent, realTarget,
311 realPoint);
312 break;
313 case (MouseEvent.MOUSE_WHEEL):
314 redispatchMouseWheelEvent((MouseWheelEvent) originalEvent,
315 realTarget, layer);
316 break;
317 }
318 dispatchMouseEvent(newEvent);
319 }
320 }
321
322 /**
323 * Re-dispatch a MouseWheelEvent. Two steps are performed:
324 * <ol>
325 * <li>Invoke
326 * {@link #transformMouseWheelEvent(MouseWheelEvent, Component, JXLayer)}
327 * creates a new event.</li>
328 * <li>Invoke {@link #processMouseWheelEvent(MouseWheelEvent, JXLayer)} with
329 * the new event.</li>
330 * </ol>
331 *
332 * @param mouseWheelEvent
333 * the event
334 * @param target
335 * the target component
336 * @param layer
337 * the layer
338 */
339 private void redispatchMouseWheelEvent(MouseWheelEvent mouseWheelEvent,
340 Component target, JXLayer<V> layer) {
341 MouseWheelEvent newEvent = this.transformMouseWheelEvent(
342 mouseWheelEvent, target, layer);
343 processMouseWheelEvent(newEvent, layer);
344 }
345
346 private MouseEvent transformMouseEvent(JXLayer<V> layer,
347 MouseEvent mouseEvent, Component target, Point realPoint) {
348 return transformMouseEvent(layer, mouseEvent, target, realPoint,
349 mouseEvent.getID());
350 }
351
352 private MouseEvent transformMouseEvent(JXLayer<V> layer,
353 MouseEvent mouseEvent, Component target, Point targetPoint, int id) {
354 if (target == null) {
355 return null;
356 } else {
357 Point newPoint = new Point(targetPoint);
358 SwingUtilities.convertPointToScreen(newPoint, layer.getView());
359 SwingUtilities.convertPointFromScreen(newPoint, target);
360 return new MouseEvent(target, //
361 id, //
362 mouseEvent.getWhen(), //
363 mouseEvent.getModifiers(), //
364 newPoint.x, //
365 newPoint.y, //
366 mouseEvent.getClickCount(), //
367 mouseEvent.isPopupTrigger(), //
368 mouseEvent.getButton());
369 }
370 }
371
372 /**
373 * Transforms a {@code MouseWheelEvent} to a new event with the right
374 * coordinates and the target source component.
375 *
376 * @param mouseWheelEvent
377 * the event
378 * @param target
379 * the target component
380 * @param layer
381 * the layer
382 *
383 * @return a new event
384 */
385 private MouseWheelEvent transformMouseWheelEvent(
386 MouseWheelEvent mouseWheelEvent, Component target, JXLayer<V> layer) {
387 if (target == null) {
388 target = layer;
389 }
390 Point point = SwingUtilities.convertPoint(mouseWheelEvent
391 .getComponent(), mouseWheelEvent.getPoint(), target);
392 MouseWheelEvent newEvent = new MouseWheelEvent(target, //
393 mouseWheelEvent.getID(), //
394 mouseWheelEvent.getWhen(), //
395 mouseWheelEvent.getModifiers(), //
396 point.x, //
397 point.y, //
398 mouseWheelEvent.getClickCount(), //
399 mouseWheelEvent.isPopupTrigger(), //
400 mouseWheelEvent.getScrollType(), //
401 mouseWheelEvent.getScrollAmount(), //
402 mouseWheelEvent.getWheelRotation() //
403 );
404 return newEvent;
405 }
406
407 /**
408 * Return some component in this hierarchy that serves as the start for a
409 * search for deeper components.
410 *
411 * @param layer
412 * The layer
413 * @return a component
414 */
415 protected abstract Component getTopLevelUserComponent(JXLayer<V> layer);
416
417 /**
418 * When transformations are used, transform a point in device space to a
419 * point in component space.
420 *
421 * @param layer
422 * The layer
423 * @param point
424 * A point
425 * @return a transformed point
426 */
427 protected abstract Point transformPoint(JXLayer<V> layer, Point point);
428
429 }