/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.andork.awt.event;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.ListCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.tree.TreeCellRenderer;
/**
* This is an advanced Swing hack. It sends fake mouse events to components
* based upon real mouse input. It can thus be used to send fake mouse events to
* components inside {@link ListCellRenderer}, {@link TreeCellRenderer}, and
* {@link TableCellRenderer} components, and make them behave as if they were
* actually part of the component hierarchy and receiving events naturally.
*
* @author andy.edwards
*/
public abstract class MouseRetargeter extends MouseAdapter implements MouseMotionListener, MouseWheelListener {
private static boolean DEBUG = false;
private Component rolloverComponent;
private Component heldComponent;
private MouseEvent holdEvent;
private MouseListener unretargetedMouseListener;
private MouseMotionListener unretargetedMouseMotionListener;
private MouseWheelListener unretargetedMouseWheelListener;
/**
* Converts a point from the coordinate system of the {@link Component} this
* {@code MouseRetargeter} is listening to to the coordinate system of a
* {@link Component} the event will be retargeted to.
*
* @param origComp
* the {@link Component} that fired a {@link MouseEvent} to one
* of the listener methods of this {@code MouseRetargeter}.
* @param origPoint
* the original location of the {@code MouseEvent}.
* @param newTarget
* the {@link Component} that the event will be retargeted to.
* @return a point representing the corresponding location in
* {@code newTarget}'s coordinate system.
*/
protected abstract Point convertPoint(Component origComp, Point origPoint, Component newTarget);
/**
* Determines what component the given {@link MouseEvent} is over. For
* example, for a cell tracker with buttons inside of it, this would figure
* out the button that is painted under the event location. This way, even
* if the buttons are not in the component hierarchy and receiving events
* naturally (because cell renderers are used for painting and removed from
* the component hierarchy afterward), we can dispatch fake mouse events to
* the buttons so they behave as if they are part of the component
* hierarchy.
*
* @param e
* @return
*/
protected abstract Component getDeepestComponent(MouseEvent e);
private Component getDeepestComponentBesidesSource(MouseEvent e) {
Component result = getDeepestComponent(e);
if (result == e.getSource()) {
result = null;
}
return result;
}
private void handleMouseMovedOrDragged(MouseEvent e) {
if (!shouldRetarget(e)) {
handleUnretargetedEvent(e);
return;
}
Component deepest = getDeepestComponentBesidesSource(e);
if (heldComponent != null && deepest != heldComponent) {
deepest = null;
}
if (deepest != rolloverComponent) {
if (rolloverComponent != null) {
retarget(e, rolloverComponent, MouseEvent.MOUSE_EXITED);
}
rolloverComponent = deepest;
if (rolloverComponent != null) {
retarget(e, rolloverComponent, MouseEvent.MOUSE_ENTERED);
}
} else if (rolloverComponent != null) {
retarget(e, rolloverComponent);
}
if (rolloverComponent == null) {
handleUnretargetedEvent(e);
}
}
protected void handleUnretargetedEvent(MouseEvent e) {
switch (e.getID()) {
case MouseEvent.MOUSE_CLICKED:
if (unretargetedMouseListener != null) {
unretargetedMouseListener.mouseClicked(e);
}
return;
case MouseEvent.MOUSE_PRESSED:
if (unretargetedMouseListener != null) {
unretargetedMouseListener.mousePressed(e);
}
return;
case MouseEvent.MOUSE_RELEASED:
if (unretargetedMouseListener != null) {
unretargetedMouseListener.mouseReleased(e);
}
return;
case MouseEvent.MOUSE_ENTERED:
if (unretargetedMouseListener != null) {
unretargetedMouseListener.mouseEntered(e);
}
return;
case MouseEvent.MOUSE_EXITED:
if (unretargetedMouseListener != null) {
unretargetedMouseListener.mouseExited(e);
}
return;
case MouseEvent.MOUSE_MOVED:
if (unretargetedMouseMotionListener != null) {
unretargetedMouseMotionListener.mouseMoved(e);
}
return;
case MouseEvent.MOUSE_DRAGGED:
if (unretargetedMouseMotionListener != null) {
unretargetedMouseMotionListener.mouseDragged(e);
}
return;
case MouseEvent.MOUSE_WHEEL:
if (unretargetedMouseWheelListener != null) {
unretargetedMouseWheelListener.mouseWheelMoved((MouseWheelEvent) e);
}
return;
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (!shouldRetarget(e)) {
handleUnretargetedEvent(e);
return;
}
Component deepest = getDeepestComponentBesidesSource(e);
if (deepest != null) {
retarget(e, deepest);
} else {
handleUnretargetedEvent(e);
}
}
@Override
public void mouseDragged(MouseEvent e) {
handleMouseMovedOrDragged(e);
}
@Override
public void mouseEntered(MouseEvent e) {
handleMouseMovedOrDragged(e);
}
@Override
public void mouseExited(MouseEvent e) {
handleMouseMovedOrDragged(e);
}
@Override
public void mouseMoved(MouseEvent e) {
handleMouseMovedOrDragged(e);
}
@Override
public void mousePressed(MouseEvent e) {
if (!shouldRetarget(e)) {
handleUnretargetedEvent(e);
return;
}
if (holdEvent == null) {
holdEvent = e;
heldComponent = getDeepestComponentBesidesSource(e);
if (heldComponent != null) {
retarget(e, heldComponent);
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (!shouldRetarget(e)) {
handleUnretargetedEvent(e);
return;
}
if (holdEvent != null) {
if (e.getButton() == holdEvent.getButton()) {
holdEvent = null;
if (heldComponent != null) {
retarget(e, heldComponent);
}
heldComponent = null;
}
} else {
handleUnretargetedEvent(e);
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (!shouldRetarget(e)) {
handleUnretargetedEvent(e);
return;
}
Component deepest = getDeepestComponentBesidesSource(e);
if (heldComponent != null && heldComponent != deepest) {
deepest = null;
}
if (deepest != null) {
retarget(e, deepest);
} else {
handleUnretargetedEvent(e);
}
}
protected void retarget(MouseEvent e, Component target) {
retarget(e, target, e.getID());
}
protected void retarget(MouseEvent e, Component target, int newID) {
Point p = convertPoint(e.getComponent(), e.getPoint(), target);
MouseEvent m = new MouseEvent(target, newID, e.getWhen(), e.getModifiers(),
p.x, p.y, e.getClickCount(), e.isPopupTrigger());
if (DEBUG) {
System.out.println("Retargeting: " + e);
System.out.println(" to: " + m);
System.out.println();
}
target.dispatchEvent(m);
}
protected void retarget(MouseWheelEvent e, Component target) {
Point p = convertPoint(e.getComponent(), e.getPoint(), target);
MouseWheelEvent m = new MouseWheelEvent(target, e.getID(), e.getWhen(), e.getModifiers(),
p.x, p.y, e.getClickCount(), e.isPopupTrigger(), e.getScrollType(),
e.getScrollAmount(), e.getWheelRotation());
if (DEBUG) {
System.out.println("Retargeting: " + e);
System.out.println(" to: " + m);
System.out.println();
}
target.dispatchEvent(m);
}
public void setUnretargetedMouseListener(Object listener) {
if (listener instanceof MouseListener) {
unretargetedMouseListener = (MouseListener) listener;
}
if (listener instanceof MouseMotionListener) {
unretargetedMouseMotionListener = (MouseMotionListener) listener;
}
if (listener instanceof MouseWheelListener) {
unretargetedMouseWheelListener = (MouseWheelListener) listener;
}
}
/**
* @param e
* a {@link MouseEvent} this {@code MouseRetargeter} received via
* one of its listener methods.
* @return {@code true} if {@code e} should be retargeted to another
* component.
*/
protected boolean shouldRetarget(MouseEvent e) {
return true;
}
}