/* * Copyright (c) 2009, Piet Blok * All rights reserved. * <p> * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * <p> * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * Neither the name of the copyright holder nor the names of the * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * <p> * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.pbjar.jxlayer.plaf.ext; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import javax.swing.*; import javax.swing.plaf.ComponentUI; import org.jdesktop.jxlayer.JXLayer; import org.jdesktop.jxlayer.plaf.AbstractLayerUI; import org.jdesktop.jxlayer.plaf.LayerUI; /** * This class provides for {@link MouseEvent} re-dispatching. It may be used to set a tool tip on {@link JXLayer}'s * glass pane and still have the child components receive {@link MouseEvent}s. * <p> * <b>Note:</b> A {@link MouseEventUI} instance cannot be shared and can be set * to a single {@link JXLayer} instance only. */ public class MouseEventUI<V extends JComponent> extends AbstractLayerUI<V> { static { /* * The instantiating of a JInternalFrame before a MouseEventUI is set to * a JXLayer containing a JDesktopPane that has no JInternalFrames set, * prevents the failing of re dispatched MouseEvents. * * This is a work around a problem that I don't really understand. * Please see * http://forums.java.net/jive/thread.jspa?threadID=66763&tstart=0 for a * discussion on this problem. */ new JInternalFrame(); } private Component lastEnteredTarget, lastPressedTarget; private boolean dispatchingMode = false; private JXLayer<? extends V> installedLayer; /** * Overridden to override the {@link LayerUI} implementation that only consults the view. * <p> * This implementation is a copy of the {@link ComponentUI#contains(JComponent, int, int)} method. */ @SuppressWarnings("deprecation") @Override public boolean contains(final JComponent c, final int x, final int y) { return c.inside(x, y); } /** * Overridden to check if this {@link LayerUI} has not been installed already, and to set the argument {@code * component} as the installed {@link JXLayer}. * * @throws IllegalStateException when this {@link LayerUI} has been installed already * @see #getInstalledLayer() */ @SuppressWarnings("unchecked") @Override public void installUI(final JComponent component) throws IllegalStateException { super.installUI(component); if (installedLayer != null) { throw new IllegalStateException(this.getClass().getName() + " cannot be shared between multiple layers"); } installedLayer = (JXLayer<? extends V>) component; } /** * Overridden to remove the installed {@link JXLayer}. */ @Override public void uninstallUI(final JComponent c) { installedLayer = null; super.uninstallUI(c); } /** * Overridden to only get the following event types: {@link AWTEvent#MOUSE_EVENT_MASK}, {@link * AWTEvent#MOUSE_MOTION_EVENT_MASK} and {@link AWTEvent#MOUSE_WHEEL_EVENT_MASK}. */ @Override public long getLayerEventMask() { return AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK; } /** * Overridden to allow for re-dispatching of mouse events to their intended (visual) recipients, rather than to the * components according to their bounds. */ @Override public void eventDispatched(final AWTEvent event, final JXLayer<? extends V> layer) { if (event instanceof MouseEvent) { MouseEvent mouseEvent = (MouseEvent) event; if (!dispatchingMode) { // Process an original mouse event dispatchingMode = true; try { redispatch(mouseEvent, layer); } finally { dispatchingMode = false; } } else { // Process a generated mouse event /* * Added a check, because on mouse entered or exited, the cursor * may be set to specific dragging cursors. */ if (MouseEvent.MOUSE_ENTERED == mouseEvent.getID() || MouseEvent.MOUSE_EXITED == mouseEvent.getID()) { layer.getGlassPane().setCursor(null); } else { Component component = mouseEvent.getComponent(); layer.getGlassPane().setCursor(component.getCursor()); } } } } /** * Re-dispatches the event to the first component in the hierarchy that has a {@link MouseWheelListener} * registered. */ @Override protected void processMouseWheelEvent(final MouseWheelEvent event, final JXLayer<? extends V> jxlayer) { /* * Only process an event if it is not already consumed. This may be the * case if this LayerUI is contained in a wrapped hierarchy. */ if (!event.isConsumed()) { /* * Since we will create a new event, the argument event must be * consumed. */ event.consume(); /* * Find a target up in the hierarchy that has * MouseWheelEventListeners registered. */ Component target = event.getComponent(); Component newTarget = findWheelListenerComponent(target); if (newTarget == null) { newTarget = jxlayer.getParent(); } /* * Convert the location relative to the new target */ Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), newTarget); /* * Create a new event and dispatch it. */ newTarget.dispatchEvent(createMouseWheelEvent(event, point, newTarget)); } } private Point calculateTargetPoint(final JXLayer<? extends V> layer, final MouseEvent mouseEvent) { Point point = mouseEvent.getPoint(); SwingUtilities.convertPointToScreen(point, mouseEvent.getComponent()); SwingUtilities.convertPointFromScreen(point, layer); return transformPoint(layer, point); } private MouseWheelEvent createMouseWheelEvent(final MouseWheelEvent mouseWheelEvent, final Point point, final Component target) { return new MouseWheelEvent(target, mouseWheelEvent.getID(), mouseWheelEvent.getWhen(), mouseWheelEvent.getModifiersEx(), point.x, point.y, mouseWheelEvent.getClickCount(), mouseWheelEvent.isPopupTrigger(), mouseWheelEvent.getScrollType(), mouseWheelEvent.getScrollAmount(), mouseWheelEvent.getWheelRotation()); } private void dispatchMouseEvent(final MouseEvent mouseEvent) { if (mouseEvent != null) { Component target = mouseEvent.getComponent(); target.dispatchEvent(mouseEvent); } } private Component findWheelListenerComponent(final Component target) { if (target == null) { return null; } else if (target.getMouseWheelListeners().length == 0) { return findWheelListenerComponent(target.getParent()); } else { return target; } } private void generateEnterExitEvents(final JXLayer<? extends V> layer, final MouseEvent originalEvent, final Component newTarget, final Point realPoint) { if (lastEnteredTarget != newTarget) { dispatchMouseEvent(transformMouseEvent(layer, originalEvent, lastEnteredTarget, realPoint, MouseEvent.MOUSE_EXITED)); lastEnteredTarget = newTarget; dispatchMouseEvent(transformMouseEvent(layer, originalEvent, lastEnteredTarget, realPoint, MouseEvent.MOUSE_ENTERED)); } } @SuppressWarnings("DuplicatedCode") private Component getListeningComponent(final MouseEvent event, final Component component) { Component comp; switch (event.getID()) { case MouseEvent.MOUSE_CLICKED : case MouseEvent.MOUSE_ENTERED : case MouseEvent.MOUSE_EXITED : case MouseEvent.MOUSE_PRESSED : case MouseEvent.MOUSE_RELEASED : comp = getMouseListeningComponent(component); break; case MouseEvent.MOUSE_DRAGGED : case MouseEvent.MOUSE_MOVED : comp = getMouseMotionListeningComponent(component); break; case MouseEvent.MOUSE_WHEEL : comp = getMouseWheelListeningComponent(component); break; default : comp = null; } return comp; } private Component getMouseListeningComponent(final Component component) { if (component.getMouseListeners().length > 0) { return component; } else { Container parent = component.getParent(); if (parent != null) { return getMouseListeningComponent(parent); } else { return null; } } } private Component getMouseMotionListeningComponent(final Component component) { /* * Mouse motion events may result in MOUSE_ENTERED and MOUSE_EXITED. * * Therefore, components with MouseListeners registered should be * returned as well. */ if (component.getMouseMotionListeners().length > 0 || component.getMouseListeners().length > 0) { return component; } else { Container parent = component.getParent(); if (parent != null) { return getMouseMotionListeningComponent(parent); } else { return null; } } } private Component getMouseWheelListeningComponent(final Component component) { if (component.getMouseWheelListeners().length > 0) { return component; } else { Container parent = component.getParent(); if (parent != null) { return getMouseWheelListeningComponent(parent); } else { return null; } } } private Component getTarget(final JXLayer<? extends V> layer, final Point targetPoint) { Component view = layer.getView(); if (view == null) { return null; } else { Point viewPoint = SwingUtilities.convertPoint(layer, targetPoint, view); return SwingUtilities.getDeepestComponentAt(view, viewPoint.x, viewPoint.y); } } @SuppressWarnings("Duplicates") private void redispatch(final MouseEvent originalEvent, final JXLayer<? extends V> layer) { if (layer.getView() != null) { if (originalEvent.getComponent() != layer.getGlassPane()) { originalEvent.consume(); } MouseEvent newEvent = null; Point realPoint = calculateTargetPoint(layer, originalEvent); Component realTarget = getTarget(layer, realPoint); if (realTarget != null) { realTarget = getListeningComponent(originalEvent, realTarget); } switch (originalEvent.getID()) { case MouseEvent.MOUSE_PRESSED : newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint); if (newEvent != null) { lastPressedTarget = newEvent.getComponent(); } break; case MouseEvent.MOUSE_RELEASED : newEvent = transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint); lastPressedTarget = null; break; case MouseEvent.MOUSE_ENTERED : case MouseEvent.MOUSE_EXITED : generateEnterExitEvents(layer, originalEvent, realTarget, realPoint); break; case MouseEvent.MOUSE_MOVED : newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint); generateEnterExitEvents(layer, originalEvent, realTarget, realPoint); break; case MouseEvent.MOUSE_DRAGGED : newEvent = transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint); generateEnterExitEvents(layer, originalEvent, realTarget, realPoint); break; case MouseEvent.MOUSE_CLICKED : newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint); break; case (MouseEvent.MOUSE_WHEEL) : redispatchMouseWheelEvent((MouseWheelEvent) originalEvent, realTarget, layer); break; } dispatchMouseEvent(newEvent); } } private void redispatchMouseWheelEvent(final MouseWheelEvent mouseWheelEvent, final Component target, final JXLayer<? extends V> layer) { MouseWheelEvent newEvent = this.transformMouseWheelEvent(mouseWheelEvent, target, layer); processMouseWheelEvent(newEvent, layer); } private MouseEvent transformMouseEvent(final JXLayer<? extends V> layer, final MouseEvent mouseEvent, final Component target, final Point realPoint) { return transformMouseEvent(layer, mouseEvent, target, realPoint, mouseEvent.getID()); } private MouseEvent transformMouseEvent(final JXLayer<? extends V> layer, final MouseEvent mouseEvent, final Component target, final Point targetPoint, final int id) { if (target == null) { return null; } else { Point newPoint = new Point(targetPoint); SwingUtilities.convertPointToScreen(newPoint, layer); SwingUtilities.convertPointFromScreen(newPoint, target); return new MouseEvent(target, id, mouseEvent.getWhen(), mouseEvent.getModifiersEx(), newPoint.x, newPoint.y, mouseEvent.getClickCount(), mouseEvent.isPopupTrigger(), mouseEvent.getButton()); } } private MouseWheelEvent transformMouseWheelEvent(final MouseWheelEvent mouseWheelEvent, final Component t, final JXLayer<? extends V> layer) { Component target = t; if (target == null) { target = layer; } Point point = SwingUtilities.convertPoint(mouseWheelEvent.getComponent(), mouseWheelEvent.getPoint(), target); return createMouseWheelEvent(mouseWheelEvent, point, target); } private Point transformPoint(final JXLayer<? extends V> layer, final Point point) { AffineTransform transform = this.getTransform(layer); if (transform != null) { try { transform.inverseTransform(point, point); } catch (NoninvertibleTransformException e) { e.printStackTrace(); } } return point; } protected JXLayer<? extends V> getInstalledLayer() { return installedLayer; } }