/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo 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 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.fge.connectors;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import org.openflexo.fge.ConnectorGraphicalRepresentation;
import org.openflexo.fge.FGEUtils;
import org.openflexo.fge.GraphicalRepresentation;
import org.openflexo.fge.connectors.ConnectorSymbol.EndSymbolType;
import org.openflexo.fge.connectors.ConnectorSymbol.MiddleSymbolType;
import org.openflexo.fge.connectors.ConnectorSymbol.StartSymbolType;
import org.openflexo.fge.cp.ConnectorAdjustingControlPoint;
import org.openflexo.fge.cp.ControlPoint;
import org.openflexo.fge.geom.FGEAbstractLine;
import org.openflexo.fge.geom.FGEGeometricObject;
import org.openflexo.fge.geom.FGEGeometricObject.Filling;
import org.openflexo.fge.geom.FGEPoint;
import org.openflexo.fge.geom.FGEQuadCurve;
import org.openflexo.fge.geom.FGERectangle;
import org.openflexo.fge.geom.FGESegment;
import org.openflexo.fge.geom.FGEShape;
import org.openflexo.fge.geom.ParallelLinesException;
import org.openflexo.fge.geom.area.FGEArea;
import org.openflexo.fge.geom.area.FGEEmptyArea;
import org.openflexo.fge.geom.area.FGEPlane;
import org.openflexo.fge.graphics.FGEConnectorGraphics;
public class CurveConnector extends Connector {
private static final Logger logger = Logger.getLogger(CurveConnector.class.getPackage().getName());
private ControlPoint cp1;
private ControlPoint cp2;
private ControlPoint cp;
private Vector<ControlPoint> controlPoints;
private FGEPoint cp1RelativeToStartObject;
private FGEPoint cp2RelativeToEndObject;
private FGEPoint cpPosition;
private boolean areBoundsAdjustable;
private boolean firstUpdated = false;
// *******************************************************************************
// * Constructor *
// *******************************************************************************
// Used for deserialization
public CurveConnector() {
this(null);
}
public CurveConnector(ConnectorGraphicalRepresentation graphicalRepresentation) {
super(graphicalRepresentation);
controlPoints = new Vector<ControlPoint>();
}
@Override
public ConnectorType getConnectorType() {
return ConnectorType.CURVE;
}
@Override
public List<ControlPoint> getControlAreas() {
return controlPoints;
}
private void updateControlPoints() {
FGEPoint newP1 = null;
FGEPoint newP2 = null;
if (areBoundsAdjustable) {
if (cp1RelativeToStartObject == null || cp2RelativeToEndObject == null) {
// To compute initial locations, we try to draw a line joining both center
// We have to compute the intersection between this line and the outline
// of joined shapes
FGEPoint centerOfEndObjectSeenFromStartObject = GraphicalRepresentation.convertNormalizedPoint(getEndObject(),
new FGEPoint(0.5, 0.5), getStartObject());
cp1RelativeToStartObject = getStartObject().getShape().outlineIntersect(centerOfEndObjectSeenFromStartObject);
if (cp1RelativeToStartObject == null) {
logger.warning("outlineIntersect() returned null");
cp1RelativeToStartObject = new FGEPoint(0.5, 0.5);
}
FGEPoint centerOfStartObjectSeenFromEndObject = GraphicalRepresentation.convertNormalizedPoint(getStartObject(),
new FGEPoint(0.5, 0.5), getEndObject());
cp2RelativeToEndObject = getEndObject().getShape().outlineIntersect(centerOfStartObjectSeenFromEndObject);
if (cp2RelativeToEndObject == null) {
logger.warning("outlineIntersect() returned null");
cp2RelativeToEndObject = new FGEPoint(0.5, 0.5);
}
}
// We have either the old position, or the default one
// We need now to find updated position according to eventual shape move, resize, reshaped, etc...
// To do that, use outlineIntersect();
if (cp1 != null) {
newP1 = cp1.getPoint();
}
cp1RelativeToStartObject = getStartObject().getShape().outlineIntersect(cp1RelativeToStartObject);
if (cp1RelativeToStartObject != null) {
newP1 = GraphicalRepresentation.convertNormalizedPoint(getStartObject(), cp1RelativeToStartObject,
getGraphicalRepresentation());
}
if (cp2 != null) {
newP2 = cp2.getPoint();
}
cp2RelativeToEndObject = getEndObject().getShape().outlineIntersect(cp2RelativeToEndObject);
if (cp2RelativeToEndObject != null) {
newP2 = GraphicalRepresentation
.convertNormalizedPoint(getEndObject(), cp2RelativeToEndObject, getGraphicalRepresentation());
}
if (cpPosition == null) {
// The 0.3 is there so that we can see the curve of the edge.
cpPosition = new FGEPoint(0.5, 0.3);
}
}
else {
// Not adjustable bounds
if (cpPosition == null) {
cpPosition = new FGEPoint(0.5, 0.4);
}
updateCPPositionIfNeeded();
FGEPoint cpPositionSeenFromStartObject = GraphicalRepresentation.convertNormalizedPoint(getGraphicalRepresentation(),
cpPosition, getStartObject());
cp1RelativeToStartObject = getStartObject().getShape().outlineIntersect(cpPositionSeenFromStartObject);
if (cp1RelativeToStartObject == null) {
logger.warning("outlineIntersect() returned null");
cp1RelativeToStartObject = new FGEPoint(0.5, 0.5);
}
FGEPoint cpPositionSeenFromEndObject = GraphicalRepresentation.convertNormalizedPoint(getGraphicalRepresentation(), cpPosition,
getEndObject());
cp2RelativeToEndObject = getEndObject().getShape().outlineIntersect(cpPositionSeenFromEndObject);
if (cp2RelativeToEndObject == null) {
logger.warning("outlineIntersect() returned null");
cp2RelativeToEndObject = new FGEPoint(0.5, 0.5);
}
newP1 = GraphicalRepresentation
.convertNormalizedPoint(getStartObject(), cp1RelativeToStartObject, getGraphicalRepresentation());
newP2 = GraphicalRepresentation.convertNormalizedPoint(getEndObject(), cp2RelativeToEndObject, getGraphicalRepresentation());
}
cp1 = new ConnectorAdjustingControlPoint(getGraphicalRepresentation(), newP1) {
@Override
public FGEArea getDraggingAuthorizedArea() {
if (getAreBoundsAdjustable()) {
FGEShape<?> shape = getStartObject().getShape().getShape();
FGEShape<?> returned = (FGEShape<?>) shape.transform(GraphicalRepresentation.convertNormalizedCoordinatesAT(
getStartObject(), getGraphicalRepresentation()));
returned.setIsFilled(false);
return returned;
} else {
return new FGEEmptyArea();
}
}
@Override
public boolean dragToPoint(FGEPoint newRelativePoint, FGEPoint pointRelativeToInitialConfiguration, FGEPoint newAbsolutePoint,
FGEPoint initialPoint, MouseEvent event) {
if (getAreBoundsAdjustable()) {
FGEPoint pt = getNearestPointOnAuthorizedArea(newRelativePoint);
setPoint(pt);
cp1RelativeToStartObject = GraphicalRepresentation.convertNormalizedPoint(getGraphicalRepresentation(), pt,
getStartObject());
refreshCurve();
getGraphicalRepresentation().notifyConnectorChanged();
}
return true;
}
};
cp2 = new ConnectorAdjustingControlPoint(getGraphicalRepresentation(), newP2) {
@Override
public FGEArea getDraggingAuthorizedArea() {
if (getAreBoundsAdjustable()) {
FGEShape<?> shape = getEndObject().getShape().getShape();
FGEShape<?> returned = (FGEShape<?>) shape.transform(GraphicalRepresentation.convertNormalizedCoordinatesAT(
getEndObject(), getGraphicalRepresentation()));
returned.setIsFilled(false);
return returned;
} else {
return new FGEEmptyArea();
}
}
@Override
public boolean dragToPoint(FGEPoint newRelativePoint, FGEPoint pointRelativeToInitialConfiguration, FGEPoint newAbsolutePoint,
FGEPoint initialPoint, MouseEvent event) {
if (getAreBoundsAdjustable()) {
FGEPoint pt = getNearestPointOnAuthorizedArea(newRelativePoint);
setPoint(pt);
cp2RelativeToEndObject = GraphicalRepresentation.convertNormalizedPoint(getGraphicalRepresentation(), pt,
getEndObject());
refreshCurve();
getGraphicalRepresentation().notifyConnectorChanged();
}
return true;
}
};
cp = new ConnectorAdjustingControlPoint(getGraphicalRepresentation(), cpPosition) {
@Override
public FGEArea getDraggingAuthorizedArea() {
return new FGEPlane();
}
@Override
public boolean dragToPoint(FGEPoint newRelativePoint, FGEPoint pointRelativeToInitialConfiguration, FGEPoint newAbsolutePoint,
FGEPoint initialPoint, MouseEvent event) {
/*
* logger.info("dragToPoint() with newRelativePoint="+newRelativePoint+" "
* +" pointRelativeToInitialConfiguration="+pointRelativeToInitialConfiguration +" newAbsolutePoint="+newAbsolutePoint
* +" initialPoint="+initialPoint);
*/
FGEPoint pt = getNearestPointOnAuthorizedArea(/* pointRelativeToInitialConfiguration */newRelativePoint);
setPoint(pt);
cpPosition = pt;
if (!getAreBoundsAdjustable()) {
updateFromNewCPPosition();
}
refreshCurve();
getGraphicalRepresentation().notifyConnectorChanged();
return true;
}
};
controlPoints.clear();
controlPoints.add(cp);
controlPoints.add(cp2);
controlPoints.add(cp1);
refreshCurve();
}
/**
* This method updates the position according to start/end motions. However, this has a small drawback which is caused by the continuous
* change of the system coordinates of the connector. Indeed, it is based on the bounds of the start and end node. If one of them moves,
* the coordinates should ideally be compared to the same coordinates system.
*/
private void updateCPPositionIfNeeded() {
if (willBeModified && previous != null) {
FGESegment newSegment = getCenterToCenterSegment();
double delta = newSegment.getAngle() - previous.getAngle();
if (Math.abs(delta) > FGEGeometricObject.EPSILON) {
FGEPoint inter;
try {
inter = FGEAbstractLine.getLineIntersection(previous, newSegment);
} catch (ParallelLinesException e) {
return;
}
FGEPoint newCPPosition = new FGEPoint();
AffineTransform at = AffineTransform.getTranslateInstance(inter.x, inter.y);
at.concatenate(AffineTransform.getRotateInstance(-delta));
at.concatenate(AffineTransform.getTranslateInstance(-inter.x, -inter.y));
at.transform(cpPosition, newCPPosition);
cpPosition = newCPPosition;
previous = newSegment;
}
}
}
private FGESegment previous;
private boolean willBeModified = false;
@Override
public void connectorWillBeModified() {
super.connectorWillBeModified();
willBeModified = true;
previous = getCenterToCenterSegment();
}
private FGESegment getCenterToCenterSegment() {
return new FGESegment(GraphicalRepresentation.convertNormalizedPoint(getStartObject(), getStartObject().getShape().getShape()
.getCenter(), getGraphicalRepresentation()), GraphicalRepresentation.convertNormalizedPoint(getEndObject(), getEndObject()
.getShape().getShape().getCenter(), getGraphicalRepresentation()));
}
@Override
public void connectorHasBeenModified() {
willBeModified = false;
previous = null;
super.connectorHasBeenModified();
};
private void updateFromNewCPPosition() {
FGEPoint cpPositionSeenFromStartObject = GraphicalRepresentation.convertNormalizedPoint(getGraphicalRepresentation(), cpPosition,
getStartObject());
cp1RelativeToStartObject = getStartObject().getShape().outlineIntersect(cpPositionSeenFromStartObject);
if (cp1RelativeToStartObject == null) {
logger.warning("outlineIntersect() returned null");
cp1RelativeToStartObject = new FGEPoint(0.5, 0.5);
}
FGEPoint cpPositionSeenFromEndObject = GraphicalRepresentation.convertNormalizedPoint(getGraphicalRepresentation(), cpPosition,
getEndObject());
cp2RelativeToEndObject = getEndObject().getShape().outlineIntersect(cpPositionSeenFromEndObject);
if (cp2RelativeToEndObject == null) {
logger.warning("outlineIntersect() returned null");
cp2RelativeToEndObject = new FGEPoint(0.5, 0.5);
}
FGEPoint newP1 = GraphicalRepresentation.convertNormalizedPoint(getStartObject(), cp1RelativeToStartObject,
getGraphicalRepresentation());
FGEPoint newP2 = GraphicalRepresentation.convertNormalizedPoint(getEndObject(), cp2RelativeToEndObject,
getGraphicalRepresentation());
cp1.setPoint(newP1);
cp2.setPoint(newP2);
}
private FGEQuadCurve curve;
private void refreshCurve() {
if (cp1 != null && cp != null && cp2 != null) {
curve = FGEQuadCurve.makeCurveFromPoints(cp1.getPoint(), cp.getPoint(), cp2.getPoint());
}
}
@Override
public void drawConnector(FGEConnectorGraphics g) {
if (!firstUpdated) {
refreshConnector();
}
g.useDefaultForegroundStyle();
if (curve != null) {
curve.paint(g);
// Draw eventual symbols
if (getGraphicalRepresentation().getStartSymbol() != StartSymbolType.NONE) {
FGESegment firstSegment = curve.getApproximatedStartTangent();
FGESegment viewSegment = firstSegment.transform(getGraphicalRepresentation().convertNormalizedPointToViewCoordinatesAT(
g.getScale()));
g.drawSymbol(firstSegment.getP1(), getGraphicalRepresentation().getStartSymbol(), getGraphicalRepresentation()
.getStartSymbolSize(), viewSegment.getAngle());
}
if (getGraphicalRepresentation().getEndSymbol() != EndSymbolType.NONE) {
FGESegment lastSegment = curve.getApproximatedEndTangent();
FGESegment viewSegment = lastSegment.transform(getGraphicalRepresentation().convertNormalizedPointToViewCoordinatesAT(
g.getScale()));
g.drawSymbol(lastSegment.getP2(), getGraphicalRepresentation().getEndSymbol(), getGraphicalRepresentation()
.getEndSymbolSize(), viewSegment.getAngle() + Math.PI);
}
if (getGraphicalRepresentation().getMiddleSymbol() != MiddleSymbolType.NONE) {
FGESegment cpSegment = curve.getApproximatedControlPointTangent();
FGESegment viewSegment = cpSegment.transform(getGraphicalRepresentation().convertNormalizedPointToViewCoordinatesAT(
g.getScale()));
g.drawSymbol(curve.getP3(), getGraphicalRepresentation().getMiddleSymbol(), getGraphicalRepresentation()
.getMiddleSymbolSize(), viewSegment.getAngle() + Math.PI);
}
}
}
@Override
public double distanceToConnector(FGEPoint aPoint, double scale) {
if (curve == null) {
logger.warning("Curve is null");
return Double.POSITIVE_INFINITY;
}
Point testPoint = getGraphicalRepresentation().convertNormalizedPointToViewCoordinates(aPoint, scale);
FGEPoint nearestPointOnCurve = curve.getNearestPoint(aPoint);
Point nearestPoint = getGraphicalRepresentation().convertNormalizedPointToViewCoordinates(nearestPointOnCurve, scale);
return testPoint.distance(nearestPoint);
}
@Override
public void refreshConnector(boolean force) {
if (!force && !needsRefresh()) {
return;
}
updateControlPoints();
super.refreshConnector(force);
firstUpdated = true;
}
@Override
public boolean needsRefresh() {
if (!firstUpdated) {
return true;
}
return super.needsRefresh();
}
@Override
public FGEPoint getMiddleSymbolLocation() {
if (cpPosition == null) {
return new FGEPoint(0, 0);
}
return cpPosition;
}
public FGEPoint _getCp1RelativeToStartObject() {
return cp1RelativeToStartObject;
}
public void _setCp1RelativeToStartObject(FGEPoint aPoint) {
this.cp1RelativeToStartObject = aPoint;
}
public FGEPoint _getCp2RelativeToEndObject() {
return cp2RelativeToEndObject;
}
public void _setCp2RelativeToEndObject(FGEPoint aPoint) {
this.cp2RelativeToEndObject = aPoint;
}
public FGEPoint _getCpPosition() {
return cpPosition;
}
public void _setCpPosition(FGEPoint cpPosition) {
this.cpPosition = cpPosition;
}
public boolean getAreBoundsAdjustable() {
return areBoundsAdjustable;
}
public void setAreBoundsAdjustable(boolean aFlag) {
if (areBoundsAdjustable != aFlag) {
areBoundsAdjustable = aFlag;
if (getGraphicalRepresentation() != null) {
updateControlPoints();
getGraphicalRepresentation().notifyConnectorChanged();
}
}
}
@Override
public FGERectangle getConnectorUsedBounds() {
if (curve == null) {
refreshCurve();
}
FGERectangle returned = new FGERectangle(Filling.FILLED);
Rectangle2D rect = curve.getBounds2D();
returned.x = rect.getX();
returned.y = rect.getY();
returned.width = rect.getWidth();
returned.height = rect.getHeight();
return returned;
}
/**
* Return start point, relative to start object
*
* @return
*/
@Override
public FGEPoint getStartLocation() {
return cp1RelativeToStartObject;
}
/**
* Return end point, relative to end object
*
* @return
*/
@Override
public FGEPoint getEndLocation() {
return cp2RelativeToEndObject;
}
@Override
public double getStartAngle() {
if (cp1 != null) {
return FGEUtils.getSlope(FGEPoint.ORIGIN_POINT, cp1.getPoint());
}
return 0;
}
@Override
public double getEndAngle() {
if (cp2 != null) {
return FGEUtils.getSlope(FGEPoint.ORIGIN_POINT, cp2.getPoint());
}
return 0;
}
@Override
public CurveConnector clone() {
CurveConnector returned = new CurveConnector(null);
returned._setCpPosition(_getCpPosition());
returned._setCp1RelativeToStartObject(_getCp1RelativeToStartObject());
returned._setCp2RelativeToEndObject(_getCp2RelativeToEndObject());
returned.setAreBoundsAdjustable(getAreBoundsAdjustable());
return returned;
}
}