package org.eclipse.gmf.tooling.runtime.linklf.editpolicies; import java.awt.BasicStroke; import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import org.eclipse.draw2d.AncestorListener; import org.eclipse.draw2d.ColorConstants; import org.eclipse.draw2d.Connection; import org.eclipse.draw2d.ConnectionAnchor; import org.eclipse.draw2d.ConnectionLocator; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Polygon; import org.eclipse.draw2d.XYAnchor; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.ConnectionEditPart; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.NodeEditPart; import org.eclipse.gef.Request; import org.eclipse.gef.commands.Command; import org.eclipse.gef.editpolicies.SelectionHandlesEditPolicy; import org.eclipse.gef.handles.ConnectionEndpointHandle; import org.eclipse.gef.requests.ReconnectRequest; /** * A selection handle policy for placing handles at the two ends of a * ConnectionEditPart. All ConnectionEditParts should have one of these, even if * the ends of the connection aren't draggable, because this is the primary * SelectionEditPolicy for showing focus. * <P> * A connection can receive focus but not selection by pressing * <code>Control+/</code> on the keyboard. * * @since 2.0 */ public class LinksLFConnectionEndPointEditPolicy extends SelectionHandlesEditPolicy { private ConnectionAnchor originalAnchor; private FeedbackHelperEx feedbackHelper; private ConnectionFocus focus; class ConnectionFocus extends Polygon implements PropertyChangeListener { AncestorListener ancestorListener = new AncestorListener.Stub() { public void ancestorMoved(IFigure ancestor) { revalidate(); } }; ConnectionFocus() { setFill(false); setForegroundColor(ColorConstants.red); setXOR(true); setOutline(true); } public void addNotify() { super.addNotify(); getConnection().addPropertyChangeListener(Connection.PROPERTY_POINTS, this); getConnection().addAncestorListener(ancestorListener); } protected void outlineShape(Graphics g) { g.setLineDash(new int[] { 1, 1 }); super.outlineShape(g); } public void propertyChange(PropertyChangeEvent evt) { revalidate(); } public void removeNotify() { getConnection().removePropertyChangeListener(Connection.PROPERTY_POINTS, this); getConnection().removeAncestorListener(ancestorListener); super.removeNotify(); } public void validate() { if (isValid()) return; PointList points = getConnection().getPoints().getCopy(); getConnection().translateToAbsolute(points); points = StrokePointListEx.strokeList(points, 5); translateToRelative(points); setPoints(points); } } /** * @see org.eclipse.gef.editpolicies.SelectionHandlesEditPolicy#createSelectionHandles() */ protected List createSelectionHandles() { List list = new ArrayList(); list.add(new ConnectionEndpointHandle((ConnectionEditPart) getHost(), ConnectionLocator.SOURCE)); list.add(new ConnectionEndpointHandle((ConnectionEditPart) getHost(), ConnectionLocator.TARGET)); return list; } /** * Erases connection move feedback. This method is called when a * ReconnectRequest is received. * * @param request * the reconnect request. */ protected void eraseConnectionMoveFeedback(ReconnectRequest request) { if (originalAnchor == null) return; if (request.isMovingStartAnchor()) getConnection().setSourceAnchor(originalAnchor); else getConnection().setTargetAnchor(originalAnchor); originalAnchor = null; feedbackHelper = null; } /** * @see org.eclipse.gef.EditPolicy#eraseSourceFeedback(org.eclipse.gef.Request) */ public void eraseSourceFeedback(Request request) { if (REQ_RECONNECT_TARGET.equals(request.getType()) || REQ_RECONNECT_SOURCE.equals(request.getType())) eraseConnectionMoveFeedback((ReconnectRequest) request); } /** * @see org.eclipse.gef.EditPolicy#getCommand(org.eclipse.gef.Request) */ public Command getCommand(Request request) { return null; } /** * Convenience method for obtaining the host's <code>Connection</code> * figure. * * @return the Connection figure */ protected Connection getConnection() { return (Connection) ((GraphicalEditPart) getHost()).getFigure(); } /** * Lazily creates and returns the feedback helper for the given request. The * helper will be configured as either moving the source or target end of * the connection. * * @param request * the reconnect request * @return the feedback helper */ protected FeedbackHelperEx getFeedbackHelper(ReconnectRequest request) { if (feedbackHelper == null) { feedbackHelper = new FeedbackHelperEx(); feedbackHelper.setConnection(getConnection()); feedbackHelper.setMovingStartAnchor(request.isMovingStartAnchor()); } return feedbackHelper; } /** * Hides the focus indicator. The focus indicator is a dotted outline around * the connection. * * @see #showFocus() * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#hideFocus() */ protected void hideFocus() { if (focus != null) { removeFeedback(focus); focus = null; } } /** * Shows or updates connection move feedback. Called whenever a show * feedback request is received for reconnection. * * @param request * the reconnect request */ protected void showConnectionMoveFeedback(ReconnectRequest request) { NodeEditPart node = null; if (request.getTarget() instanceof NodeEditPart) node = (NodeEditPart) request.getTarget(); if (originalAnchor == null) { if (request.isMovingStartAnchor()) originalAnchor = getConnection().getSourceAnchor(); else originalAnchor = getConnection().getTargetAnchor(); } ConnectionAnchor anchor = null; if (node != null) { if (request.isMovingStartAnchor()) anchor = node.getSourceConnectionAnchor(request); else anchor = node.getTargetConnectionAnchor(request); } FeedbackHelperEx helper = getFeedbackHelper(request); helper.update(anchor, request.getLocation()); } /** * Shows focus around the connection. * * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#showFocus() */ protected void showFocus() { if (focus == null) { focus = new ConnectionFocus(); addFeedback(focus); } } /** * @see org.eclipse.gef.EditPolicy#showSourceFeedback(org.eclipse.gef.Request) */ public void showSourceFeedback(Request request) { if (REQ_RECONNECT_SOURCE.equals(request.getType()) || REQ_RECONNECT_TARGET.equals(request.getType())) showConnectionMoveFeedback((ReconnectRequest) request); } public static class StrokePointListEx { static float segment[] = new float[6]; static PointList strokeList(PointList list, int offset) { GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO); Point p = list.getPoint(0); path.moveTo(p.x, p.y); for (int i = 1; i < list.size(); i++) path.lineTo((p = list.getPoint(i)).x, p.y); BasicStroke stroke = new BasicStroke(offset * 2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f); Shape stroked = stroke.createStrokedShape(path); Area area = new Area(stroked); PathIterator iter = area.getPathIterator(null, 10.0f); PointList currentSegment = null; PointList result = null; int largestSegmentSize = 0; while (!iter.isDone()) { if (currentSegment == null) currentSegment = new PointList(list.size() * 2); int type = iter.currentSegment(segment); currentSegment.addPoint(Math.round(segment[0]), Math.round(segment[1])); iter.next(); if (type == PathIterator.SEG_CLOSE) { if (currentSegment.size() > largestSegmentSize) { result = currentSegment; largestSegmentSize = currentSegment.size(); currentSegment = null; } } } return result; } } /** * Helps display connection feedback during drags of the connection ends. This * class is used internally by the provided EditPolicy implementation. * * @author hudsonr */ public class FeedbackHelperEx { private Connection connection; private XYAnchorEx dummyAnchor; private boolean isMovingStartAnchor = false; /** * Constructs a new FeedbackHelper. */ public FeedbackHelperEx() { dummyAnchor = new XYAnchorEx(new Point(10, 10)); } /** * Returns the connection being used to show feedback. * * @return the connection */ protected Connection getConnection() { return connection; } /** * Returns true is the feedback is moving the source anchor * * @return <code>true</code> if moving start */ protected boolean isMovingStartAnchor() { return isMovingStartAnchor; } /** * Sets the connection. * * @param c * connection */ public void setConnection(Connection c) { connection = c; } /** * Sets if moving start of connection. * * @param value * <code>true</code> if the starts is being moved */ public void setMovingStartAnchor(boolean value) { isMovingStartAnchor = value; } /** * Sets the anchor for the end being moved. * * @param anchor * the new anchor */ protected void setAnchor(ConnectionAnchor anchor) { if (isMovingStartAnchor()) getConnection().setSourceAnchor(anchor); else getConnection().setTargetAnchor(anchor); } /** * Updates the feedback based on the given anchor or point. The anchor is * used unless <code>null</code>. The point is used when there is no anchor. * * @param anchor * <code>null</code> or an anchor * @param p * the point to use when there is no anchor */ public void update(ConnectionAnchor anchor, Point p) { if (anchor != null) setAnchor(anchor); else { dummyAnchor.setLocation(p); setAnchor(dummyAnchor); } } } public static class XYAnchorEx extends XYAnchor { public XYAnchorEx(Point p) { super(p); } private IFigure myOwner; @Override public IFigure getOwner() { if (myOwner == null) { myOwner = new Figure(); myOwner.setBounds(new Rectangle(getReferencePoint().x - 1, getReferencePoint().y - 1, 2, 2)); } return myOwner; } } }