blob: 5654890bc9b46e24e46e5855370b00f5fc17996b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* SAP AG - Changed for initial API, implementation and documentation of Graphiti:
* made fields NULL_CONSTRAINT, originalConstraint, isDeleting, ref1, ref2 protected
* made methods protected:
* createHandlesForAutomaticBendpoints
* createHandlesForUserBendpoints
* isAutomaticallyBending
* lineContainsPoint
* setReferencePoints
* added
* getConfigurationProvider
* getFeatureProvider
* setConfigurationProvider
* checkMoveAndRemoveBendpointFeature
* checkCreateBendpointFeature
*******************************************************************************/
// ****************************************************************************
// ****************************************************************************
// THIS CLASS IS MOSTLY COPIED FROM BendpointEditPolicy
// BUT EVERYTHING IS protected, SO THAT IT CAN BE OVERWRITTEN
// ****************************************************************************
// ****************************************************************************
package org.eclipse.graphiti.ui.internal.policy;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.AbsoluteBendpoint;
import org.eclipse.draw2d.AutomaticRouter;
import org.eclipse.draw2d.Bendpoint;
import org.eclipse.draw2d.Connection;
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.EditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.SelectionHandlesEditPolicy;
import org.eclipse.gef.handles.BendpointCreationHandle;
import org.eclipse.gef.handles.BendpointHandle;
import org.eclipse.gef.handles.BendpointMoveHandle;
import org.eclipse.gef.requests.BendpointRequest;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.context.impl.MoveBendpointContext;
import org.eclipse.graphiti.features.context.impl.RemoveBendpointContext;
import org.eclipse.graphiti.internal.services.GraphitiInternal;
import org.eclipse.graphiti.mm.pictograms.FreeFormConnection;
import org.eclipse.graphiti.ui.internal.config.IConfigurationProviderInternal;
/**
* Used to add bendpoint handles on a {@link ConnectionEditPart}.
* <P>
* BendpointEditPolicy will automatically observe the
* {@link org.eclipse.draw2d.Connection} figure. If the number of bends in the
* <code>Connection</code> changes, the handles will be updated.
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public abstract class BendpointEditPolicyFixed extends SelectionHandlesEditPolicy implements PropertyChangeListener {
protected static final List<Bendpoint> NULL_CONSTRAINT = new ArrayList<Bendpoint>();
// CHANGED: make fields protected
protected List<?> originalConstraint;
protected boolean isDeleting = false;
private IConfigurationProviderInternal configurationProvider;
protected static final Point ref1 = new Point();
protected static final Point ref2 = new Point();
public BendpointEditPolicyFixed(IConfigurationProviderInternal configurationProvider) {
super();
setConfigurationProvider(configurationProvider);
}
/**
* <code>activate()</code> is extended to add a listener to the
* <code>Connection</code> figure.
*
* @see org.eclipse.gef.EditPolicy#activate()
*/
@Override
public void activate() {
super.activate();
getConnection().addPropertyChangeListener(Connection.PROPERTY_POINTS, this);
}
protected List<BendpointHandle> createHandlesForAutomaticBendpoints() {
List<BendpointHandle> list = new ArrayList<BendpointHandle>();
ConnectionEditPart connEP = (ConnectionEditPart) getHost();
PointList points = getConnection().getPoints();
for (int i = 0; i < points.size() - 1; i++)
list.add(new BendpointCreationHandle(connEP, 0, i));
return list;
}
protected List<BendpointHandle> createHandlesForUserBendpoints() {
List<BendpointHandle> list = new ArrayList<BendpointHandle>();
ConnectionEditPart connEP = (ConnectionEditPart) getHost();
PointList points = getConnection().getPoints();
List<?> bendPoints = (List<?>) getConnection().getRoutingConstraint();
int bendPointIndex = 0;
Point currBendPoint = null;
if (bendPoints == null) {
bendPoints = NULL_CONSTRAINT;
} else if (!bendPoints.isEmpty()) {
currBendPoint = ((Bendpoint) bendPoints.get(0)).getLocation();
}
for (int i = 0; i < points.size() - 1; i++) {
if (checkCreateBendpointFeature()) {
// Put a create handle on the middle of every segment
list.add(new BendpointCreationHandle(connEP, bendPointIndex, i));
}
// If the current user bendpoint matches a bend location, show a
// move handle
if (i < points.size() - 1 && bendPointIndex < bendPoints.size() && currBendPoint.equals(points.getPoint(i + 1))) {
if (checkMoveAndRemoveBendpointFeature()) {
list.add(new BendpointMoveHandle(connEP, bendPointIndex, i + 1));
}
// Go to the next user bendpoint
bendPointIndex++;
if (bendPointIndex < bendPoints.size()) {
currBendPoint = ((Bendpoint) bendPoints.get(bendPointIndex)).getLocation();
}
}
}
return list;
}
/**
* Creates selection handles for the bendpoints. Explicit (user-defined)
* bendpoints will have {@link BendpointMoveHandle}s on them with a single
* {@link BendpointCreationHandle} between 2 consecutive explicit
* bendpoints. If implicit bendpoints (such as those created by the
* {@link AutomaticRouter}) are used, one {@link BendpointCreationHandle} is
* placed in the middle of the Connection.
*
* @see SelectionHandlesEditPolicy#createSelectionHandles()
*/
@Override
protected List<BendpointHandle> createSelectionHandles() {
List<BendpointHandle> list = new ArrayList<BendpointHandle>();
boolean automaticallyBending = isAutomaticallyBending();
if (automaticallyBending) {
list = createHandlesForAutomaticBendpoints();
} else {
list = createHandlesForUserBendpoints();
}
return list;
}
/**
* <code>deactivate()</code> is extended to remove the property change
* listener on the <code>Connection</code> figure.
*
* @see org.eclipse.gef.EditPolicy#deactivate()
*/
@Override
public void deactivate() {
getConnection().removePropertyChangeListener(Connection.PROPERTY_POINTS, this);
super.deactivate();
}
/**
* Erases all bendpoint feedback. Since the original <code>Connection</code>
* figure is used for feedback, we just restore the original constraint that
* was saved before feedback started to show.
*
* @param request
* the BendpointRequest
*/
protected void eraseConnectionFeedback(BendpointRequest request) {
restoreOriginalConstraint();
request.getSource().refresh();
originalConstraint = null;
}
/**
* @see org.eclipse.gef.EditPolicy#eraseSourceFeedback(Request)
*/
@Override
public void eraseSourceFeedback(Request request) {
if (REQ_MOVE_BENDPOINT.equals(request.getType()) || REQ_CREATE_BENDPOINT.equals(request.getType()))
eraseConnectionFeedback((BendpointRequest) request);
}
/**
* Factors the Request into either a MOVE, a DELETE, or a CREATE of a
* bendpoint.
*
* @see org.eclipse.gef.EditPolicy#getCommand(Request)
*/
@Override
public Command getCommand(Request request) {
if (REQ_MOVE_BENDPOINT.equals(request.getType())) {
if (isDeleting) {
return getDeleteBendpointCommand((BendpointRequest) request);
}
return getMoveBendpointCommand((BendpointRequest) request);
}
if (REQ_CREATE_BENDPOINT.equals(request.getType())) {
return getCreateBendpointCommand((BendpointRequest) request);
}
return null;
}
/**
* Convenience method for obtaining the host's <code>Connection</code>
* figure.
*
* @return the Connection figure
*/
protected Connection getConnection() {
return (Connection) ((ConnectionEditPart) getHost()).getFigure();
}
/**
* Implement this method to return a Command that will create a bendpoint.
*
* @param request
* the BendpointRequest
* @return a Command to create a bendpoint
*/
protected abstract Command getCreateBendpointCommand(BendpointRequest request);
/**
* Implement this method to return a Command that will delete a bendpoint.
*
* @param request
* the BendpointRequest
* @return a Command to delete a bendpoint
*/
protected abstract Command getDeleteBendpointCommand(BendpointRequest request);
/**
* Implement this method to return a Command that will move a bendpoint.
*
* @param request
* the BendpointRequest
* @return a Command to move a bendpoint
*/
protected abstract Command getMoveBendpointCommand(BendpointRequest request);
protected boolean isAutomaticallyBending() {
List<?> constraint = (List<?>) getConnection().getRoutingConstraint();
PointList points = getConnection().getPoints();
return ((points.size() > 2) && (constraint == null || constraint.isEmpty()));
}
protected boolean lineContainsPoint(Point p1, Point p2, Point p) {
int tolerance = 7;
Rectangle rect = Rectangle.SINGLETON;
rect.setSize(0, 0);
rect.setLocation(p1.x, p1.y);
rect.union(p2.x, p2.y);
rect.expand(tolerance, tolerance);
if (!rect.contains(p.x, p.y))
return false;
int v1x, v1y, v2x, v2y;
int numerator, denominator;
double result = 0.0;
if (p1.x != p2.x && p1.y != p2.y) {
v1x = p2.x - p1.x;
v1y = p2.y - p1.y;
v2x = p.x - p1.x;
v2y = p.y - p1.y;
numerator = v2x * v1y - v1x * v2y;
denominator = v1x * v1x + v1y * v1y;
result = ((numerator << 10) / denominator * numerator) >> 10;
}
// if it is the same point, and it passes the bounding box test,
// the result is always true.
return result <= tolerance * tolerance;
}
/**
* If the number of bendpoints changes, handles are updated.
*
* @see java.beans.PropertyChangeListener#propertyChange(PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt) {
// $TODO optimize so that handles aren't added constantly.
if (getHost().getSelected() != EditPart.SELECTED_NONE)
addSelectionHandles();
}
/**
* Restores the original constraint that was saved before feedback began to
* show.
*/
protected void restoreOriginalConstraint() {
if (originalConstraint != null) {
if (originalConstraint == NULL_CONSTRAINT)
getConnection().setRoutingConstraint(null);
else
getConnection().setRoutingConstraint(originalConstraint);
}
}
/**
* Since the original figure is used for feedback, this method saves the
* original constraint, so that is can be restored when the feedback is
* erased.
*/
protected void saveOriginalConstraint() {
originalConstraint = (List<?>) getConnection().getRoutingConstraint();
if (originalConstraint == null)
originalConstraint = NULL_CONSTRAINT;
getConnection().setRoutingConstraint(new ArrayList<Object>(originalConstraint));
}
protected void setReferencePoints(BendpointRequest request) {
PointList points = getConnection().getPoints();
int bpIndex = -1;
List<?> bendPoints = (List<?>) getConnection().getRoutingConstraint();
Point bp = ((Bendpoint) bendPoints.get(request.getIndex())).getLocation();
double smallestDistance = -1;
for (int i = 0; i < points.size(); i++) {
double dist = points.getPoint(i).getDistance(bp);
if (smallestDistance == -1 || dist < smallestDistance) {
bpIndex = i;
smallestDistance = dist;
if (smallestDistance == 0)
break;
}
}
points.getPoint(ref1, bpIndex - 1);
getConnection().translateToAbsolute(ref1);
points.getPoint(ref2, bpIndex + 1);
getConnection().translateToAbsolute(ref2);
}
/**
* Shows feedback when a bendpoint is being created. The original figure is
* used for feedback and the original constraint is saved, so that it can be
* restored when feedback is erased.
*
* @param request
* the BendpointRequest
*/
protected void showCreateBendpointFeedback(BendpointRequest request) {
Point p = new Point(request.getLocation());
List<Bendpoint> constraint;
getConnection().translateToRelative(p);
Bendpoint bp = new AbsoluteBendpoint(p);
if (originalConstraint == null) {
saveOriginalConstraint();
constraint = getConnectionRoutingConstraint();
constraint.add(request.getIndex(), bp);
} else {
constraint = getConnectionRoutingConstraint();
}
constraint.set(request.getIndex(), bp);
getConnection().setRoutingConstraint(constraint);
}
/**
* Shows feedback when a bendpoint is being deleted. This method is only
* called once when the bendpoint is first deleted, not every mouse move.
* The original figure is used for feedback and the original constraint is
* saved, so that it can be restored when feedback is erased.
*
* @param request
* the BendpointRequest
*/
protected void showDeleteBendpointFeedback(BendpointRequest request) {
if (originalConstraint == null) {
saveOriginalConstraint();
List<?> constraint = (List<?>) getConnection().getRoutingConstraint();
constraint.remove(request.getIndex());
getConnection().setRoutingConstraint(constraint);
}
}
/**
* Shows feedback when a bendpoint is being moved. Also checks to see if the
* bendpoint should be deleted and then calls
* {@link #showDeleteBendpointFeedback(BendpointRequest)} if needed. The
* original figure is used for feedback and the original constraint is
* saved, so that it can be restored when feedback is erased.
*
* @param request
* the BendpointRequest
*/
protected void showMoveBendpointFeedback(BendpointRequest request) {
Point p = new Point(request.getLocation());
if (!isDeleting)
setReferencePoints(request);
if (lineContainsPoint(ref1, ref2, p)) {
if (!isDeleting) {
isDeleting = true;
eraseSourceFeedback(request);
showDeleteBendpointFeedback(request);
}
return;
}
if (isDeleting) {
isDeleting = false;
eraseSourceFeedback(request);
}
if (originalConstraint == null)
saveOriginalConstraint();
List<Bendpoint> constraint = getConnectionRoutingConstraint();
getConnection().translateToRelative(p);
Bendpoint bp = new AbsoluteBendpoint(p);
constraint.set(request.getIndex(), bp);
getConnection().setRoutingConstraint(constraint);
}
/**
* Shows feedback when appropriate. Calls a different method depending on
* the request type.
*
* @see #showCreateBendpointFeedback(BendpointRequest)
* @see #showMoveBendpointFeedback(BendpointRequest)
* @param request
* the Request
*/
@Override
public void showSourceFeedback(Request request) {
if (REQ_MOVE_BENDPOINT.equals(request.getType()))
showMoveBendpointFeedback((BendpointRequest) request);
else if (REQ_CREATE_BENDPOINT.equals(request.getType()))
showCreateBendpointFeedback((BendpointRequest) request);
}
protected IConfigurationProviderInternal getConfigurationProvider() {
return configurationProvider;
}
protected IFeatureProvider getFeatureProvider() {
if (getConfigurationProvider() != null) {
return getConfigurationProvider().getFeatureProvider();
}
return null;
}
private void setConfigurationProvider(IConfigurationProviderInternal configurationProvider) {
this.configurationProvider = configurationProvider;
}
private boolean checkMoveAndRemoveBendpointFeature() {
if (!(getConnection() instanceof FreeFormConnection)) {
return true;
}
boolean ret = false;
FreeFormConnection ffc = (FreeFormConnection) getConnection();
if (!GraphitiInternal.getEmfService().isObjectAlive(ffc)) {
return ret;
}
ret = (null != getFeatureProvider().getMoveBendpointFeature(new MoveBendpointContext(null)));
ret = ret || (null != getFeatureProvider().getRemoveBendpointFeature(new RemoveBendpointContext(ffc, null)));
return ret;
}
private boolean checkCreateBendpointFeature() {
if (!(getConnection() instanceof FreeFormConnection)) {
return true;
}
boolean ret = false;
FreeFormConnection ffc = (FreeFormConnection) getConnection();
if (!GraphitiInternal.getEmfService().isObjectAlive(ffc)) {
return ret;
}
ret = (null != getFeatureProvider().getMoveBendpointFeature(new MoveBendpointContext(null)));
return ret;
}
protected List<Bendpoint> getConnectionRoutingConstraint() {
Object rawRoutingConstraint = getConnection().getRoutingConstraint();
@SuppressWarnings("unchecked")
List<Bendpoint> ret = (List<Bendpoint>) rawRoutingConstraint;
return ret;
}
}