/*
* Copyright (c) 2002-@year@, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* 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 University of Maryland nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior written permission.
*
* 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.
*
* Piccolo was written at the Human-Computer Interaction Laboratory www.cs.umd.edu/hcil by Jesse Grosjean
* under the supervision of Ben Bederson. The Piccolo website is www.cs.umd.edu/hcil/piccolo.
*/
package edu.umd.cs.piccolo.util;
import java.awt.geom.Dimension2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import javax.swing.event.EventListenerList;
import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.event.PInputEventListener;
/**
* <b>PPickPath</b> represents a ordered list of nodes that have been picked.
* The topmost ancestor node is the first node in the list (and should be a camera),
* the bottommost child node is at the end of the list. It is this bottom node that
* is given first chance to handle events, and that any active event handlers usually
* manipulate.
* <p>
* Note that because of layers (which can be picked by multiple camera's) the ordered
* list of nodes in a pick path do not all share a parent child relationship with the
* nodes in the list next to them. This means that the normal localToGlobal methods don't
* work when trying to transform geometry up and down the pick path, instead you should
* use the pick paths canvasToLocal methods to get the mouse event points into your local
* coord system.
* <p>
* Note that PInputEvent wraps most of the useful PPickPath methods, so often you
* can use a PInputEvent directly instead of having to access its pick path.
* <p>
* @see edu.umd.cs.piccolo.event.PInputEvent
* @version 1.0
* @author Jesse Grosjean
*/
public class PPickPath implements PInputEventListener {
public static PPickPath CURRENT_PICK_PATH;
private static double[] PTS = new double[4];
private PStack nodeStack;
private PStack transformStack;
private PStack pickBoundsStack;
private PCamera topCamera;
private PCamera bottomCamera;
private HashMap excludedNodes;
public PPickPath(PCamera aCamera, PBounds aScreenPickBounds) {
super();
pickBoundsStack = new PStack();
topCamera = aCamera;
nodeStack = new PStack();
transformStack = new PStack();
pickBoundsStack.push(aScreenPickBounds);
CURRENT_PICK_PATH = this;
}
public PBounds getPickBounds() {
return (PBounds) pickBoundsStack.peek();
}
public boolean acceptsNode(PNode node) {
if (excludedNodes != null) {
return !excludedNodes.containsKey(node);
}
return true;
}
//****************************************************************
// Picked Nodes
//****************************************************************
public void pushNode(PNode aNode) {
nodeStack.push(aNode);
}
public void popNode(PNode aNode) {
nodeStack.pop();
}
/**
* Get the bottom node on the pick path node stack. That is the last node to
* be picked.
*/
public PNode getPickedNode() {
return (PNode) nodeStack.peek();
}
//****************************************************************
// Iterating over picked nodes.
//****************************************************************
/**
* Return the next node that will be picked after the current picked node.
* For instance of you have two overlaping children nodes then the topmost
* child will always be picked first, use this method to find the covered child.
* Return the camera when no more visual will be picked.
*/
public PNode nextPickedNode() {
PNode picked = getPickedNode();
if (picked == topCamera) return null;
if (excludedNodes == null) excludedNodes = new HashMap();
// exclude current picked node
excludedNodes.put(picked, picked);
Object screenPickBounds = pickBoundsStack.get(0);
// reset path state
pickBoundsStack = new PStack();
nodeStack = new PStack();
transformStack = new PStack();
pickBoundsStack = new PStack();
pickBoundsStack.push(screenPickBounds);
// pick again
topCamera.fullPick(this);
// make sure top camera is pushed.
if (getNodeStackReference().size() == 0) {
pushNode(topCamera);
pushTransform(topCamera.getTransformReference(false));
}
return getPickedNode();
}
/**
* Get the top camera on the pick path. This is the camera that originated the
* pick action.
*/
public PCamera getTopCamera() {
return topCamera;
}
/**
* Get the bottom camera on the pick path. This may be different then the top
* camera if internal cameras are in use.
*/
public PCamera getBottomCamera() {
if (bottomCamera == null) {
for (int i = nodeStack.size() - 1; i >= 0; i--) {
PNode each = (PNode) nodeStack.get(i);
if (each instanceof PCamera) {
bottomCamera = (PCamera) each;
return bottomCamera;
}
}
}
return bottomCamera;
}
public PStack getNodeStackReference() {
return nodeStack;
}
//****************************************************************
// Path Transform
//****************************************************************
public double getScale() {
PTS[0] = 0;//x1
PTS[1] = 0;//y1
PTS[2] = 1;//x2
PTS[3] = 0;//y2
int count = transformStack.size();
for (int i = 0; i < count; i++) {
PAffineTransform each = ((PTuple)transformStack.get(i)).transform;
if (each != null)
each.transform(PTS, 0, PTS, 0, 2);
}
return Point2D.distance(PTS[0], PTS[1], PTS[2], PTS[3]);
}
public void pushTransform(PAffineTransform aTransform) {
transformStack.push(new PTuple(getPickedNode(), aTransform));
if (aTransform != null) {
Rectangle2D newPickBounds = (Rectangle2D) getPickBounds().clone();
aTransform.inverseTransform(newPickBounds, newPickBounds);
pickBoundsStack.push(newPickBounds);
}
}
public void popTransform(PAffineTransform aTransform) {
transformStack.pop();
if (aTransform != null) {
pickBoundsStack.pop();
}
}
public PAffineTransform getPathTransformTo(PNode nodeOnPath) {
PAffineTransform aTransform = new PAffineTransform();
int count = transformStack.size();
for (int i = 0; i < count; i++) {
PTuple each = (PTuple) transformStack.get(i);
if (each.transform != null) aTransform.concatenate(each.transform);
if (nodeOnPath == each.node) {
return aTransform;
}
}
throw new RuntimeException("Node could not be found on pick path");
}
//****************************************************************
// Process Events - Give each node in the pick path, starting at
// the bottom most one, a chance to handle the event.
//****************************************************************
public void processEvent(PInputEvent aEvent, int type) {
aEvent.setPath(this);
for (int i = nodeStack.size() - 1; i >= 0; i--) {
PNode each = (PNode) nodeStack.get(i);
EventListenerList list = each.getListenerList();
if (list != null) {
Object[] listeners = list.getListeners(PInputEventListener.class);
for (int j = 0; j < listeners.length; j++) {
PInputEventListener listener = (PInputEventListener) listeners[j];
listener.processEvent(aEvent, type);
}
}
}
}
//****************************************************************
// Transforming Geometry - Methods to transform geometry through
// this path.
// <p>
// Note that this is different that just using the
// PNode.localToGlobal (an other coord system transform methods).
// The PNode coord system transform methods always go directly up
// through their parents. The PPickPath coord system transform
// methods go up through the list of picked nodes instead. And since
// cameras can pick their layers in addition to their children these
// two paths may be different.
//****************************************************************
/**
* Convert the given point from the canvas coordinates, down through
* the pick path (and through any camera view transforms applied to the
* path) to the local coordinates of the given node.
*/
public Point2D canvasToLocal(Point2D canvasPoint, PNode nodeOnPath) {
try {
return getPathTransformTo(nodeOnPath).inverseTransform(canvasPoint, canvasPoint);
} catch (NoninvertibleTransformException e) {
e.printStackTrace();
}
return null;
}
/**
* Convert the given dimension from the canvas coordinates, down through
* the pick path (and through any camera view transforms applied to the
* path) to the local coordinates of the given node.
*/
public Dimension2D canvasToLocal(Dimension2D canvasDimension, PNode nodeOnPath) {
return getPathTransformTo(nodeOnPath).inverseTransform(canvasDimension, canvasDimension);
}
/**
* Convert the given rectangle from the canvas coordinates, down through
* the pick path (and through any camera view transforms applied to the
* path) to the local coordinates of the given node.
*/
public Rectangle2D canvasToLocal(Rectangle2D canvasRectangle, PNode nodeOnPath) {
return getPathTransformTo(nodeOnPath).inverseTransform(canvasRectangle, canvasRectangle);
}
/**
* Used to associated nodes with their transforms on the transform stack.
*/
private static class PTuple {
public PNode node;
public PAffineTransform transform;
public PTuple(PNode n, PAffineTransform t) {
node = n;
transform = t;
}
}
}