/**
* Squidy Interaction Library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Squidy Interaction Library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Squidy Interaction Library. If not, see
* <http://www.gnu.org/licenses/>.
*
* 2009 Human-Computer Interaction Group, University of Konstanz.
* <http://hci.uni-konstanz.de>
*
* Please contact info@squidy-lib.de or visit our website
* <http://www.squidy-lib.de> for further information.
*/
package org.squidy.designer.model;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.event.KeyEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.XmlType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.squidy.designer.Designer;
import org.squidy.designer.Initializable;
import org.squidy.designer.constant.DebugConstants;
import org.squidy.designer.shape.LayoutConstraint;
import org.squidy.designer.shape.VisualShape;
import org.squidy.designer.zoom.ConnectorShape;
import org.squidy.designer.zoom.impl.DataTypeShape;
import org.squidy.designer.zoom.impl.PortShape;
import org.squidy.designer.zoom.impl.VisualizationShape;
import org.squidy.manager.Manager;
import org.squidy.manager.data.IData;
import org.squidy.manager.model.Pipe;
import org.squidy.manager.model.Piping;
import org.squidy.manager.model.Processable;
import org.squidy.manager.model.Processable.Action;
import org.squidy.manager.util.DataUtility;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolo.util.PPaintContext;
import edu.umd.cs.piccolo.util.PPickPath;
/**
* <code>Edge</code>.
*
* <pre>
* Date: Feb 19, 2009
* Time: 2:00:01 PM
* </pre>
*
* @author <pre>
* Roman Rädle
* <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a>
* Human-Computer Interaction Group
* University of Konstanz
* </pre>
*
* @version $Id: PipeShape.java 772 2011-09-16 15:39:44Z raedle $
* @since 1.0.0
*/
@XmlType(name = "PipeShape")
public class PipeShape extends VisualShape<VisualShape<?>> implements
Initializable {
/**
* Generated serial version UID.
*/
private static final long serialVersionUID = 2506753802315463256L;
// Logger to log info, error, debug,... messages.
private static final Log LOG = LogFactory.getLog(PipeShape.class);
// #############################################################################
// BEGIN CREATOR
// #############################################################################
public static PipeShape create(ConnectorShape<?, ?> source,
ConnectorShape<?, ?> target, String... ids) {
PipeShape shape = new PipeShape(source, target);
if (ids != null && ids.length == 2)
shape.setId(ids[0]);
LayoutConstraint layoutConstraint = shape.getLayoutConstraint();
layoutConstraint.setScale(0.1);
Pipe pipe = new Pipe();
if (ids != null && ids.length == 2)
pipe.setId(ids[1]);
pipe.setSource(source.getProcessable());
pipe.setTarget(target.getProcessable());
pipe.setInputTypes(new ArrayList<Class<? extends IData>>(
DataUtility.ALL_DATA_TYPES));
pipe.setOutputTypes(new ArrayList<Class<? extends IData>>(
DataUtility.ALL_DATA_TYPES));
shape.setPipe(pipe);
// TODO: [RR] Remove if pipe will be added to parent of source.
Processable sourceProcessable = source.getProcessable();
if (sourceProcessable instanceof Piping) {
((Piping) sourceProcessable).addOutgoingPipe(pipe);
}
Processable targetProcessable = target.getProcessable();
if (targetProcessable instanceof Piping) {
((Piping) targetProcessable).addIncomingPipe(pipe);
}
shape.initialize();
// TODO [RR]: This is just a hack an prevents recursive call of PipeShape.create()
if (ids.length == 0)
Designer.getInstance().add(shape);
return shape;
}
// #############################################################################
// END CREATOR
// #############################################################################
// #############################################################################
// BEGIN JAXB
// #############################################################################
/**
* Default constructor at least required for JAXB.
*/
public PipeShape() {
// empty
}
@XmlIDREF
@XmlAttribute(name = "pipe-ref")
private Pipe pipe;
/**
* @return the pipe
*/
public final Pipe getPipe() {
return pipe;
}
/**
* @param pipe
* the pipe to set
*/
public final void setPipe(Pipe pipe) {
this.pipe = pipe;
}
@XmlIDREF
@XmlAttribute(name = "source")
private ConnectorShape<?, ?> source;
public ConnectorShape<?, ?> getSource() {
return source;
}
public void setSource(ConnectorShape<?, ?> source) {
this.source = source;
}
@XmlIDREF
@XmlAttribute(name = "target")
private ConnectorShape<?, ?> target;
public ConnectorShape<?, ?> getTarget() {
return target;
}
public void setTarget(ConnectorShape<?, ?> target) {
this.target = target;
}
// #############################################################################
// END JAXB
// #############################################################################
// #############################################################################
// BEGIN Initializable
// #############################################################################
/*
* (non-Javadoc)
*
* @see org.squidy.designer.Initializable#initialize()
*/
public void initialize() {
if (pipe == null) {
if (LOG.isErrorEnabled()) {
LOG
.error("Could not initialize PipeShape without a pipe instance. Removing from parent.");
}
return;
}
visualization = new VisualizationShape(this);
visualization.setScale(0.5);
addChild(visualization);
// ShapeUtils.setApparent(visualization, false);
pipe.addProcessingFeedback(visualization);
flowIncoming = new DataTypeShape(pipe.getInputTypes());
flowIncoming.setScale(0.1);
addChild(flowIncoming);
// ShapeUtils.setApparent(flowIncoming, false);
flowOutgoing = new DataTypeShape(pipe.getOutputTypes());
flowOutgoing.setScale(0.1);
addChild(flowOutgoing);
// ShapeUtils.setApparent(flowOutgoing, false);
final PropertyChangeListener changeListener = new PropertyChangeListener() {
/*
* (non-Javadoc)
*
*
* @seejava.beans.PropertyChangeListener#propertyChange(java.
* beans. PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() != null) {
setBounds(computeBounds());
positionVisualization();
}
}
};
addPropertyChangeListener(PNode.PROPERTY_PARENT, changeListener);
source.addPropertyChangeListener(PNode.PROPERTY_FULL_BOUNDS, changeListener);
target.addPropertyChangeListener(PNode.PROPERTY_FULL_BOUNDS, changeListener);
pipe.addStatusChangeListener(Processable.STATUS_PROCESSABLE_DELETED,
new PropertyChangeListener() {
/*
* (non-Javadoc)
*
* @see
* java.beans.PropertyChangeListener#propertyChange(java
* .beans.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt) {
if (source != null) {
source.removePropertyChangeListener(changeListener);
}
if (target != null) {
target.removePropertyChangeListener(changeListener);
}
removeFromParent();
}
});
addInputEventListener(new PBasicInputEventHandler() {
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.piccolo.event.PBasicInputEventHandler#mouseEntered
* (edu.umd.cs.piccolo.event.PInputEvent)
*/
@Override
public void mouseEntered(PInputEvent event) {
super.mouseEntered(event);
// ShapeUtils.setApparent(visualization, true);
// ShapeUtils.setApparent(flowIncoming, true);
// ShapeUtils.setApparent(flowOutgoing, true);
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.piccolo.event.PBasicInputEventHandler#mouseExited(
* edu.umd.cs.piccolo.event.PInputEvent)
*/
@Override
public void mouseExited(PInputEvent event) {
super.mouseExited(event);
// ShapeUtils.setApparent(visualization, false);
// ShapeUtils.setApparent(flowIncoming, false);
// ShapeUtils.setApparent(flowOutgoing, false);
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.piccolo.event.PBasicInputEventHandler#mousePressed
* (edu.umd.cs.piccolo. event.PInputEvent)
*/
@Override
public void mousePressed(PInputEvent event) {
super.mousePressed(event);
if (!event.isHandled()) {
Point2D p = event.getPosition();
double x = p.getX();
double y = p.getY();
// Check intersection based on mouse position (5 pixel
// around
// position)
// Rectangle2D rectangle = globalToLocal(new
// Rectangle2D.Double(x, y, 100 * getGlobalScale(),
// 100 * getGlobalScale()));
// if (shape.intersects(rectangle)) {
event.getInputManager().setKeyboardFocus(event.getPath());
//
// if (event.isRightMouseButton()) {
// globalToLocal(p);
// System.out.println("Do you wan't to create a bendpoint at x="
// + p.getX() + " / y="
// + p.getY() + "?");
// }
event.setHandled(true);
// }
}
// PNode nextNode = event.getPath().nextPickedNode();
// if (nextNode != null) {
// EventListenerList listenerList = nextNode.getListenerList();
//
// if (listenerList != null) {
// PBasicInputEventHandler[] listeners =
// listenerList.getListeners(PBasicInputEventHandler.class);
// for (PBasicInputEventHandler listener : listeners) {
// listener.mousePressed(event);
// }
// }
// }
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.piccolo.event.PBasicInputEventHandler#keyboardFocusGained
* (edu.umd.cs.piccolo .event.PInputEvent)
*/
@Override
public void keyboardFocusGained(PInputEvent event) {
super.keyboardFocusGained(event);
selected = true;
moveToFront();
invalidatePaint();
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.piccolo.event.PBasicInputEventHandler#keyboardFocusLost
* (edu.umd.cs.piccolo .event.PInputEvent)
*/
@Override
public void keyboardFocusLost(PInputEvent event) {
super.keyboardFocusLost(event);
selected = false;
invalidatePaint();
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.piccolo.event.PBasicInputEventHandler#keyPressed(edu
* .umd.cs.piccolo.event .PInputEvent)
*/
@Override
public void keyPressed(PInputEvent event) {
super.keyPressed(event);
if (KeyEvent.VK_DELETE == event.getKeyCode()) {
// DrawingArea drawingArea = (DrawingArea)
// event.getCamera().getComponent();
// drawingArea.removeEdge(Edge.this);
if (LOG.isDebugEnabled()) {
LOG.debug("Backspace has been pressed. Trigger deletion of edge?");
}
pipe.delete();
Manager.get().notify(getPipe(), Action.DELETE);
}
}
});
}
// Rectangle2D rect;
// #############################################################################
// END Initializable
// #############################################################################
// #############################################################################
// BEGIN INTERNAL
// #############################################################################
private boolean selected = false;
public Line2D shape = new Line2D.Double();
// public CubicCurve2D shape = new CubicCurve2D.Double();
private VisualizationShape visualization;
private DataTypeShape flowIncoming;
private DataTypeShape flowOutgoing;
/**
* @param source
* @param target
*/
public PipeShape(ConnectorShape<?, ?> source, ConnectorShape<?, ?> target) {
this.source = source;
this.target = target;
}
// /*
// * (non-Javadoc)
// *
// * @see edu.umd.cs.piccolo.PNode#pick(edu.umd.cs.piccolo.util.PPickPath)
// */
// @Override
// protected boolean pick(PPickPath pickPath) {
// PBounds bounds = pickPath.getPickBounds();
// return shape.intersects(bounds);
// }
/*
* (non-Javadoc)
*
* @see
* org.squidy.designer.diagram.node.AbstractNode#pickAfterChildren
* (edu.umd.cs.piccolo.util.PPickPath)
*/
@Override
protected boolean pickAfterChildren(PPickPath pickPath) {
PBounds bounds = pickPath.getPickBounds();
bounds.add(bounds.getMinX() - 50, bounds.getMinY() - 50);
bounds.add(bounds.getMaxX() + 50, bounds.getMaxY() + 50);
return shape.intersects(bounds);
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.designer.diagram.node.AbstractNode#computeFullBounds
* (edu.umd.cs.piccolo.util.PBounds)
*/
private PBounds computeBounds() {
// TODO: [RR] Hack!!! Do not compute bounds if ports are invisible.
// if (source.getOutputPort().getScale() < 1.0 ||
// target.getOutputPort().getScale() < 1.0) {
// return getBoundsReference();
// }
PortShape sourcePortShape;
PortShape targetPortShape;
if (!source.getParent().equals(target.getParent())) {
if (source instanceof PipelineShape
&& !(target instanceof PipelineShape)) {
// ConnectorShape<?, ?> tmp = source;
// source = target;
// target = tmp;
sourcePortShape = source.getInputPort();
targetPortShape = target.getInputPort();
} else if (!(source instanceof PipelineShape)
&& target instanceof PipelineShape) {
sourcePortShape = source.getOutputPort();
targetPortShape = target.getOutputPort();
}
else {
// TODO [RR]: This may cause unexpected painting of shapes -> The visual shape drawing does not match the processing hierarchy.
sourcePortShape = source.getInputPort();
targetPortShape = target.getInputPort();
}
} else {
sourcePortShape = source.getOutputPort();
targetPortShape = target.getInputPort();
}
// System.out.println("GFB: " + source.getGlobalFullBounds());
// System.out.println("FBR: " + source.getFullBoundsReference());
// System.out.println("BR : " + source.getBoundsReference());
// System.out.println("Off: " + source.getOffset());
Point2D p1 = globalToLocal(sourcePortShape.getParent().localToGlobal(
sourcePortShape.getFullBoundsReference().getCenter2D()));
Point2D p2 = globalToLocal(targetPortShape.getParent().localToGlobal(
targetPortShape.getFullBoundsReference().getCenter2D()));
double x1 = p1.getX();
double x2 = p2.getX();
double y1 = p1.getY();
double y2 = p2.getY();
// Rectangle2D bounds1 =
// globalToLocal(sourcePortShape.getGlobalFullBounds());
// Rectangle2D bounds2 =
// globalToLocal(targetPortShape.getGlobalFullBounds());
//
// double x1 = bounds1.getMinX();
// double x2 = bounds2.getMinX();
// double y1 = bounds1.getMinY();
// double y2 = bounds2.getMinY();
double x = Math.min(x1, x2);
double y = Math.min(y1, y2);
double width = Math.max(Math.abs(x1 - x2), 1);
double height = Math.max(Math.abs(y1 - y2), 1);
// Rectangle2D b = localToParent(new PBounds(x, y, width, height));
// shape.setCurve(x1, y1, x1 + (width / 2), y1, x2 - (width / 2), y2,
// x2, y2);
shape.setLine(x1, y1 - LINE_WIDTH / 2.0 - 10, x2, y2 - LINE_WIDTH / 2.0
- 10);
return new PBounds(shape.getBounds());
}
/**
* Position the <code>ZoomVisualization</code> at the middle of the edge.
*/
private void positionVisualization() {
PBounds bounds = getBoundsReference();
// System.out.println(bounds.getCenterY());
PBounds visualizationBounds = visualization.getBounds();
visualization.localToParent(visualizationBounds);
visualization.setOffset(bounds.getCenterX()
- (visualizationBounds.getWidth() / 2), bounds.getCenterY()
- (visualizationBounds.getHeight() / 2));
double x1 = shape.getX1();
double y1 = shape.getY1();
double x2 = shape.getX2();
double y2 = shape.getY2();
PBounds flowIncomingBounds = flowIncoming.getBounds();
flowIncoming.localToParent(flowIncomingBounds);
PBounds flowOutgoingBounds = flowOutgoing.getBounds();
flowOutgoing.localToParent(flowOutgoingBounds);
// PBounds flowIncomingBounds = flowIncoming.getFullBoundsReference();
// PBounds flowOutgoingBounds = flowOutgoing.getFullBoundsReference();
if (x1 < x2) {
x1 += (bounds.getWidth() / 4) - (flowIncomingBounds.getWidth() / 2);
x2 -= (bounds.getWidth() / 4) + (flowOutgoingBounds.getWidth() / 2);
} else {
x1 -= (bounds.getWidth() / 4) + (flowIncomingBounds.getWidth() / 2);
x2 += (bounds.getWidth() / 4) - (flowOutgoingBounds.getWidth() / 2);
}
if (y1 < y2) {
y1 += (bounds.getHeight() / 4)
- (flowIncomingBounds.getHeight() / 2);
y2 -= (bounds.getHeight() / 4)
+ (flowOutgoingBounds.getHeight() / 2);
} else {
y1 -= (bounds.getHeight() / 4)
+ (flowIncomingBounds.getHeight() / 2);
y2 += (bounds.getHeight() / 4)
- (flowOutgoingBounds.getHeight() / 2);
}
flowIncoming.setOffset(x1, y1);
flowOutgoing.setOffset(x2, y2);
// double theta = bounds.getHeight() / bounds.getWidth();
// theta = Math.atan(theta);
//
// System.out.println("THETA: " + theta);
//
// flowIncoming.setRotation(theta);
// double theta = bounds.getHeight() / bounds.getWidth();
// flowIncoming.transformBy(AffineTransform.getRotateInstance(theta,
// flowIncomingBounds.getCenterX(), flowIncomingBounds.getCenterY()));
//
// double rotationTheta = Math.sin(visualizationBounds.getHeight() /
// visualizationBounds.getWidth()) * Math.PI;
// visualization.setRotation(rotationTheta);
}
private static final double LINE_WIDTH = 70.0;
private static final Color COLOR_NORMAL = new Color(120, 120, 120, 50);
private static final Color COLOR_PROCESSING = new Color(0, 255, 0, 50);
private static final Color COLOR_FAILURE = new Color(255, 0, 0, 50);
private static final Color COLOR_NORMAL_SELECTED = new Color(120, 120, 120);
private static final Color COLOR_PROCESSING_SELECTED = new Color(0, 255, 0);
private static final Color COLOR_FAILURE_SELECTED = new Color(255, 0, 0);
private static final Stroke STROKE_SHAPE[] = new Stroke[3];
static {
STROKE_SHAPE[0] = new BasicStroke((float) LINE_WIDTH,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
STROKE_SHAPE[1] = new BasicStroke((float) (LINE_WIDTH - 10.0),
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
STROKE_SHAPE[2] = new BasicStroke((float) (LINE_WIDTH - 20.0),
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.designer.VisualShape#paintShape(edu.umd.cs.piccolo.
* util.PPaintContext)
*/
@Override
protected void paintShape(PPaintContext paintContext) {
super.paintShape(paintContext);
Graphics2D g = paintContext.getGraphics();
boolean processing = getPipe().getSource().isProcessing();
boolean hasFailure = getSource().hasFailure();
if (selected) {
g.setColor(hasFailure ? COLOR_FAILURE_SELECTED
: (processing ? COLOR_PROCESSING_SELECTED
: COLOR_NORMAL_SELECTED));
} else {
g.setColor(hasFailure ? COLOR_FAILURE
: (processing ? COLOR_PROCESSING : COLOR_NORMAL));
}
for (int i = 0; i < 3; i++) {
g.setStroke(STROKE_SHAPE[i]);
g.draw(shape);
}
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.designer.VisualShape#paintDebug(edu.umd.cs.piccolo.
* util.PPaintContext)
*/
@Override
protected void paintDebug(PPaintContext paintContext) {
super.paintDebug(paintContext);
Graphics2D g = paintContext.getGraphics();
if (DebugConstants.PAINTING_BOUNDS) {
g.setColor(DebugConstants.COLOR_BOUNDS);
g.draw(getBoundsReference());
}
if (DebugConstants.PAINTING_FULL_BOUNDS) {
g.setColor(DebugConstants.COLOR_FULL_BOUNDS);
g.draw(getFullBounds());
}
if (DebugConstants.PAINTING_CENTER) {
g.setColor(DebugConstants.COLOR_CENTER);
PBounds bounds = getBoundsReference();
g.fillOval((int) bounds.getCenterX() - 5,
(int) bounds.getCenterY() - 5, 10, 10);
}
}
// #############################################################################
// END INTERNAL
// #############################################################################
}