/** * Created on 12-Feb-2004 */ package pipe.views; import pipe.actions.gui.PipeApplicationModel; import pipe.constants.GUIConstants; import pipe.controllers.ArcController; import pipe.controllers.PetriNetController; import pipe.handlers.ArcPathPointHandler; import pipe.utilities.gui.GuiUtils; import pipe.utilities.math.Cubic; import uk.ac.imperial.pipe.exceptions.PetriNetComponentException; import uk.ac.imperial.pipe.models.petrinet.*; import uk.ac.imperial.pipe.visitor.component.PetriNetComponentVisitor; import java.awt.*; import java.awt.geom.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; /** * Class that displays the path of an arc graphically on screen. * It's old code so needs tidying up at some point */ public class ArcPath implements Shape, Cloneable { private static final Stroke PROXIMITY_STROKE = new BasicStroke(GUIConstants.ARC_PATH_PROXIMITY_WIDTH); private static final Stroke STROKE = new BasicStroke(GUIConstants.ARC_PATH_SELECTION_WIDTH); /** * The midpoint along the arc, used to display the arc weights here if necessary */ public final Point2D.Double midPoint = new Point2D.Double(); /** * Points along the arc path */ private final List<ArcPathPoint> pathPoints = new ArrayList<>(); /** * Parent view who the points belong to */ private final ArcView<? extends Connectable, ? extends Connectable> arcView; /** * Petri net controller for which the arc belongs to */ private final PetriNetController petriNetController; /** * Main PIPE application model */ private final PipeApplicationModel applicationModel; /** * Graphical representation of the path */ private GeneralPath path = new GeneralPath(); /** * When pointlock is on no points will be displayed when the cursor is hovered * over them. Nor will they be dragable */ private boolean pointLock = false; private Shape shape = STROKE.createStrokedShape(this); private Shape proximityShape = PROXIMITY_STROKE.createStrokedShape(this); /** * Angle at which to meet a transition */ private int transitionAngle; /** * Constructor * * @param arcView arc who these points belong to * @param petriNetController Petri net controller for which the underlying arc belongs to * @param applicationModel main PIPE application model */ public ArcPath(ArcView<? extends Connectable, ? extends Connectable> arcView, PetriNetController petriNetController, PipeApplicationModel applicationModel) { this.arcView = arcView; this.petriNetController = petriNetController; this.applicationModel = applicationModel; transitionAngle = 0; } /** * Note: We cannot do this in O(1) time using a HashMap because ArcPoints are * mutable objects, meaning that they could change whilst keys in the HashMap * * @param point to be evaluated * @return true if the path contains the point */ public boolean contains(ArcPoint point) { for (ArcPathPoint p : pathPoints) { if (p.getModel().equals(point)) { return true; } } return false; } /** * Remove the point from the graphical representation of the path * * @param point to be deleted */ public void deletePoint(ArcPoint point) { ArcPathPoint pointView = null; for (ArcPathPoint p : pathPoints) { if (p.getModel().equals(point)) { pointView = p; } } if (pointView != null) { pointView.delete(); pathPoints.remove(pointView); } } /** * @return number of arc points in the path */ public int getNumPoints() { return pathPoints.size(); } /** * @param index of the point * @return the location of the point at this index, or null if it does not exist */ public Point2D getPoint(int index) { return pathPoints.get(index).getPoint(); } /** * @param index of the point * @return the point at this index or null if it does not exist */ public ArcPathPoint getPathPoint(int index) { return pathPoints.get(index); } /** * Graphically show the points along the path */ public void showPoints() { if (!pointLock) { for (ArcPathPoint pathPoint : pathPoints) { pathPoint.setVisible(true); } } } /** * @param lock true if points should always be shown on the canvas */ public void setPointVisibilityLock(boolean lock) { pointLock = lock; } /** * Hide all points on the canvas */ public void hidePoints() { if (!pointLock) { for (ArcPathPoint pathPoint : pathPoints) { if (!pathPoint.isSelected()) { pathPoint.setVisible(false); } } } } /** * @return the angle at which the arc leaves its source */ public double getStartAngle() { if (getEndIndex() > 0) { return pathPoints.get(0).getAngle(pathPoints.get(1).getControl()); } return 0; } /** * @return the index of the last item in the path points */ public int getEndIndex() { return pathPoints.size() - 1; } /** * Used because there is no layout manager for the canvas * * @return the bounds of the arc path */ @Override public Rectangle getBounds() { return path.getBounds(); } @Override public Rectangle2D getBounds2D() { return null; } /** * @param x coordinate * @param y coordinate * @return false, no point is contained within this arc */ @Override public boolean contains(double x, double y) { return false; } /** * @param point to be evaluated * @return true if the point intersects the shape */ @Override public boolean contains(Point2D point) { return shape.contains(point); } /** * @param arg0 coordinate * @param arg1 coordinate * @param arg2 coordinate * @param arg3 coordinate * @return false always false */ @Override public boolean intersects(double arg0, double arg1, double arg2, double arg3) { return false; } /** * @param rect rectangle * @return true if the rectangle intersects the shape */ @Override public boolean intersects(Rectangle2D rect) { return shape.intersects(rect); } /** * @param arg0 coordinate * @param arg1 coordinate * @param arg2 coordinate * @param arg3 coordinate * @return false always false */ @Override public boolean contains(double arg0, double arg1, double arg2, double arg3) { return false; } /** * @param rect rectangle * @return false always false */ @Override public boolean contains(Rectangle2D rect) { return false; } /** * @param arg0 affine transform * @return an iterator for the path */ @Override public PathIterator getPathIterator(AffineTransform arg0) { return path.getPathIterator(arg0); } @Override public PathIterator getPathIterator(AffineTransform arg0, double arg1) { return path.getPathIterator(arg0, arg1); } public boolean proximityContains(Point2D p) { return proximityShape.contains(p); } /** * Tells the arc points to remove themselves */ public void delete() { while (!pathPoints.isEmpty()) { // force delete of ALL points // pathPoints.get(0).kill(); ArcPathPoint remove = pathPoints.remove(0); remove.kill(); } } /** * @param point point to add to path * @param index position in the path */ public void insertIntermediatePoint(ArcPoint point, int index) { insertPoint(index, createPoint(point)); } /** * insertPoint() * Inserts a new point into the Array List of path points * at the specified index and shifts all the following points along * * @param index of point in list * @param newpoint to be added * @author Nadeem */ public void insertPoint(int index, ArcPathPoint newpoint) { pathPoints.add(index, newpoint); addPointsToGui(arcView.getParent()); } /** * @param point to be created * @return a graphical point at the underlying point models location */ private ArcPathPoint createPoint(ArcPoint point) { PropertyChangeListener listener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent propertyChangeEvent) { createPath(); arcView.updateBounds(); arcView.repaint(); } }; point.addPropertyChangeListener(listener); return new ArcPathPoint(point, this, petriNetController, arcView.getParent()); } /** * Add all graphical arc points to the Petri net tab * * @param petriNetTab container to add points to */ public void addPointsToGui(Container petriNetTab) { if (petriNetTab == null) { //Parent has not yet been added return; } for (ArcPathPoint point : pathPoints) { point.setVisible(false); // Check whether the point has already been added to the gui // as addPointsToGui() may have been called after the user // split an existing point. If this is the case, we don't want // to add all the points again along with new action listeners, // we just want to add the new point. // Nadeem 21/06/2005 // if (petriNetTab.getIndexOf(point) < 0) { petriNetTab.add(point, new Integer(300)); //TODO SEPERATE HANDLERS INTO THOSE THAT NEED THE CONTROLLER! ArcController<? extends Connectable, ? extends Connectable> arcController = petriNetController.getArcController(arcView.getModel()); ArcPathPointHandler pointHandler = new ArcPathPointHandler(petriNetTab, point, petriNetController, arcController, applicationModel); if (point.getMouseListeners().length == 0) { point.addMouseListener(pointHandler); } if (point.getMouseMotionListeners().length == 0) { point.addMouseMotionListener(pointHandler); } if (point.getMouseWheelListeners().length == 0) { point.addMouseWheelListener(pointHandler); } } } /** * Creates the path layout using the path points set */ public void createPath() { setControlPoints(); path.reset(); ArcPathPoint currentPoint = pathPoints.get(0); setStartingPoint(currentPoint); double length = 0; for (int point = 1; point <= getEndIndex(); point++) { ArcPathPoint previousPoint = currentPoint; currentPoint = pathPoints.get(point); if (!currentPoint.isCurved()) { createStraightPoint(currentPoint); } else if (currentPoint.isCurved()) { createCurvedPoint(currentPoint); } length += getLength(currentPoint.getPoint(), previousPoint.getPoint()); } setMidPoint(length); shape = STROKE.createStrokedShape(this); proximityShape = PROXIMITY_STROKE.createStrokedShape(this); } /** * Set the control points for the Bezier curves */ private void setControlPoints() { //must be in this order setCurveControlPoints(); setStraightControlPoints(); setEndControlPoints(); } /** * Moves the path to the first point specified on the arc * @param point where path starts */ private void setStartingPoint(ArcPathPoint point) { path.moveTo(point.getPoint().getX(), point.getPoint().getY()); } /** * Creates a straight line * * @param point point to create line to */ private void createStraightPoint(ArcPathPoint point) { path.lineTo(point.getPoint().getX(), point.getPoint().getY()); } /** * Creates a curved line * * @param point point to create curve to */ private void createCurvedPoint(ArcPathPoint point) { path.curveTo(point.getControl1().x, point.getControl1().y, point.getControl().x, point.getControl().y, point.getPoint().getX(), point.getPoint().getY()); } /** * @param A first point * @param B second point * @return modulus of vector A -> B */ private double getLength(Point2D A, Point2D B) { double ABx = A.getX() - B.getX(); double ABy = A.getY() - B.getY(); return Math.sqrt(ABx * ABx + ABy * ABy); } /** * Sets the midpoint which is used to relocate the arcs label * * @param length path length */ private void setMidPoint(double length) { ArcPathPoint currentPoint = pathPoints.get(0); if (getEndIndex() < 2) { midPoint.x = (pathPoints.get(0).getPoint().getX() + pathPoints.get(1).getPoint().getX()) * 0.5; midPoint.y = (pathPoints.get(0).getPoint().getY() + pathPoints.get(1).getPoint().getY()) * 0.5; } else { double acc = 0; double percent = 0; ArcPathPoint previousPoint = currentPoint; for (int point = 1; point < pathPoints.size(); point++) { previousPoint = currentPoint; currentPoint = pathPoints.get(point); double inc = getLength(currentPoint.getPoint(), previousPoint.getPoint()); double halfLength = length / 2.0; if (acc + inc > halfLength) { percent = (halfLength - acc) / inc; break; } acc += inc; } midPoint.x = previousPoint.getPoint().getX() + (float) ( (currentPoint.getPoint().getX() - previousPoint.getPoint().getX()) * percent); midPoint.y = previousPoint.getPoint().getY() + (float) ( (currentPoint.getPoint().getY() - previousPoint.getPoint().getY()) * percent); } } /* function sets control points for any curved sections of the path */ private void setCurveControlPoints() { if (pathPoints.isEmpty()) { return; } Cubic[] X; Cubic[] Y; int c = 1; while (c < pathPoints.size()) { int curveStartIndex; int curveEndIndex = 0; ArcPathPoint currentPoint = pathPoints.get(c); if (currentPoint.isCurved()) { curveStartIndex = c - 1; for (; c < pathPoints.size() && currentPoint.isCurved(); c++) { currentPoint = pathPoints.get(c); curveEndIndex = c; } /* calculate a cubic for each section of the curve */ int lengthOfCurve = curveEndIndex - curveStartIndex; int k1; int[] x = new int[lengthOfCurve + 2]; int[] y = new int[lengthOfCurve + 2]; for (k1 = 0; k1 <= (curveEndIndex - curveStartIndex); k1++) { x[k1] = (int) (pathPoints.get(curveStartIndex + k1)).getPoint().getX(); y[k1] = (int) (pathPoints.get(curveStartIndex + k1)).getPoint().getY(); } x[k1] = x[k1 - 1]; y[k1] = y[k1 - 1]; X = calcNaturalCubic(k1, x); Y = calcNaturalCubic(k1, y); for (int k2 = 1; k2 <= lengthOfCurve; k2++) { currentPoint = pathPoints.get(k2 + curveStartIndex); currentPoint.setControl1(X[k2 - 1].getX1(), Y[k2 - 1].getX1()); currentPoint.setControl2(X[k2 - 1].getX2(), Y[k2 - 1].getX2()); } } else { c++; } } } /* fuction sets the control points for any straight sections and for smooth * intersection between straight and curved sections */ private void setStraightControlPoints() { ArcPathPoint myPreviousButOnePoint; for (int c = 1; c <= getEndIndex(); c++) { ArcPathPoint previousPoint = pathPoints.get(c - 1); ArcPathPoint currentPoint = pathPoints.get(c); if (!currentPoint.isCurved()) { currentPoint.setControl1( getControlPoint(previousPoint.getPoint(), currentPoint.getPoint(), previousPoint.getPoint(), currentPoint.getPoint()) ); currentPoint.setControl( getControlPoint(currentPoint.getPoint(), previousPoint.getPoint(), currentPoint.getPoint(), previousPoint.getPoint()) ); } else { if (c > 1 && !previousPoint.isCurved()) { myPreviousButOnePoint = pathPoints.get(c - 2); currentPoint.setControl1(getControlPoint(myPreviousButOnePoint.getPoint(), previousPoint.getPoint(), previousPoint.getPoint(), currentPoint.getPoint())); } if (c < getEndIndex()) { ArcPathPoint nextPoint = pathPoints.get(c + 1); if (!nextPoint.isCurved()) { currentPoint.setControl( getControlPoint(nextPoint.getPoint(), currentPoint.getPoint(), currentPoint.getPoint(), previousPoint.getPoint()) ); } } } } } /** * Set the control points for the end of the arc */ private void setEndControlPoints() { PetriNetComponentVisitor endPointVisitor = new ArcConnectableVisitor(); Connectable source = getArc().getModel().getSource(); try { source.accept(endPointVisitor); } catch (PetriNetComponentException e) { GuiUtils.displayErrorMessage(null, e.getMessage()); } } /** * We solve the equation * [2 1 ] [D[0]] [3(x[1] - x[0]) ] * |1 4 1 | |D[1]| |3(x[2] - x[0]) | * | 1 4 1 | | . | = | . | * | ..... | | . | | . | * | 1 4 1| | . | |3(x[n] - x[n-2])| * [ 1 2] [D[n]] [3(x[n] - x[n-1])] * <p> * by using row operations to convert the matrix to upper triangular * and then back substitution. The D[i] are the derivatives at the knots. * </p> * @param n number of rows * @param x offset * @return a natural cubic for the Bezier curve */ private Cubic[] calcNaturalCubic(int n, int[] x) { float[] gamma = new float[n + 1]; float[] delta = new float[n + 1]; float[] D = new float[n + 1]; gamma[0] = 1.0f / 2.0f; for (int i = 1; i < n; i++) { gamma[i] = 1 / (4 - gamma[i - 1]); } gamma[n] = 1 / (2 - gamma[n - 1]); delta[0] = 3 * (x[1] - x[0]) * gamma[0]; for (int i = 1; i < n; i++) { delta[i] = (3 * (x[i + 1] - x[i - 1]) - delta[i - 1]) * gamma[i]; } delta[n] = (3 * (x[n] - x[n - 1]) - delta[n - 1]) * gamma[n]; D[n] = delta[n]; for (int i = n - 1; i >= 0; i--) { D[i] = delta[i] - gamma[i] * D[i + 1]; } /* now compute the coefficients of the cubics */ Cubic[] C = new Cubic[n]; for (int i = 0; i < n; i++) { C[i] = new Cubic(x[i], D[i], 3 * (x[i + 1] - x[i]) - 2 * D[i] - D[i + 1], 2 * (x[i] - x[i + 1]) + D[i] + D[i + 1]); } return C; } /* returns a control point for curve CD with incoming vector AB*/ private Point2D.Double getControlPoint(Point2D A, Point2D B, Point2D C, Point2D D) { Point2D.Double p = new Point2D.Double(0, 0); double modAB = getLength(A, B); double modCD = getLength(C, D); double ABx = (B.getX() - A.getX()) / modAB; double ABy = (B.getY() - A.getY()) / modAB; if (modAB < 7) { // hack, stops division by zero, modAB can only be this low if the // points are virtually superimposed anyway p = (Point2D.Double) C.clone(); } else { p.x = C.getX() + (ABx * modCD / GUIConstants.ARC_CONTROL_POINT_CONSTANT); p.y = C.getY() + (ABy * modCD / GUIConstants.ARC_CONTROL_POINT_CONSTANT); } return p; } /** * @return arc view associated with this point */ public ArcView<? extends Connectable, ? extends Connectable> getArc() { return arcView; } /** * Removes all path points */ public void clear() { for (ArcPathPoint pathPoint : pathPoints) { pathPoint.kill(); } pathPoints.clear(); } /** * Visitor interface that visits Places and Transitions */ private interface ConnectableVisitor extends PlaceVisitor, TransitionVisitor { } private class ArcConnectableVisitor implements ConnectableVisitor { @Override public void visit(Place place) { if (pathPoints.get(getEndIndex()).isCurved()) { double angle = Math.toRadians(transitionAngle); ArcPathPoint myPoint = pathPoints.get(getEndIndex()); ArcPathPoint myLastPoint = pathPoints.get(getEndIndex() - 1); float distance = (float) getLength(myPoint.getPoint(), myLastPoint.getPoint()) / GUIConstants.ARC_CONTROL_POINT_CONSTANT; myPoint.setControl2((float) (myPoint.getPoint().getX() + Math.cos(angle) * distance), (float) (myPoint.getPoint().getY() + Math.sin(angle) * distance)); myPoint = pathPoints.get(1); myPoint.setControl(getControlPoint(pathPoints.get(0).getPoint(), myPoint.getControl(), pathPoints.get(0).getPoint(), myPoint.getControl())); } } @Override public void visit(Transition transition) { if (pathPoints.get(1).isCurved()) { double angle = Math.toRadians(transitionAngle); ArcPathPoint myPoint = pathPoints.get(1); ArcPathPoint myLastPoint = pathPoints.get(0); float distance = (float) getLength(myPoint.getPoint(), myLastPoint.getPoint()) / GUIConstants.ARC_CONTROL_POINT_CONSTANT; myPoint.setControl1((float) (myLastPoint.getPoint().getX() + Math.cos(angle) * distance), (float) (myLastPoint.getPoint().getY() + Math.sin(angle) * distance)); myPoint = pathPoints.get(getEndIndex()); myPoint.setControl(getControlPoint(myPoint.getPoint(), myPoint.getControl1(), myPoint.getPoint(), myPoint.getControl1())); } } } }