blob: 3f394c70cca8d93aef4e324de04a80c146d63883 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2005, 2019 SAP SE
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* SAP SE - initial API, implementation and documentation
* jpasch - BUG 341180: Graphiti fails to handle resize after custom feature addition in the tutorial
* mwenz - Bug 346067: Current Milestone Build does no longer build against Eclipse 3.6
* mwenz - Bug 355027: Move of connection decorators when zoom level != 100 behaves weird
* mgorning - Bug 342262 - enhanced resize behavior for container shapes
* Bug 336488 - DiagramEditor API
* mgorning - Bug 363186 - Allow modification of selection and hover state also for anchors
* mgorning - Bug 383512 - Moving (Resizing) Problem - Polyline/Polygon on first level
* pjpaulin - Bug 352120 - Now uses IDiagramContainerUI interface
* mwenz - Bug 496822 - NullPointerException in ShapeXYLayoutEditPolicy.getMoveConnectionDecoratorCommand
* mwenz - Bug 528405 - ArithmeticException in ShapeXYLayoutEditPolicy.getSnapValue
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.graphiti.ui.internal.policy;
import java.util.Arrays;
import java.util.List;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.Request;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.editpolicies.NonResizableEditPolicy;
import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.graphiti.datatypes.IDimension;
import org.eclipse.graphiti.features.ICreateFeature;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.IMoveAnchorFeature;
import org.eclipse.graphiti.features.IMoveConnectionDecoratorFeature;
import org.eclipse.graphiti.features.IMoveShapeFeature;
import org.eclipse.graphiti.features.IResizeShapeFeature;
import org.eclipse.graphiti.features.context.ICreateContext;
import org.eclipse.graphiti.features.context.IMoveAnchorContext;
import org.eclipse.graphiti.features.context.IMoveConnectionDecoratorContext;
import org.eclipse.graphiti.features.context.IMoveShapeContext;
import org.eclipse.graphiti.features.context.IResizeShapeContext;
import org.eclipse.graphiti.features.context.impl.AreaAnchorContext;
import org.eclipse.graphiti.features.context.impl.CreateContext;
import org.eclipse.graphiti.features.context.impl.MoveConnectionDecoratorContext;
import org.eclipse.graphiti.features.context.impl.MoveShapeContext;
import org.eclipse.graphiti.features.context.impl.ResizeShapeContext;
import org.eclipse.graphiti.internal.command.CommandContainer;
import org.eclipse.graphiti.internal.command.GenericFeatureCommandWithContext;
import org.eclipse.graphiti.internal.command.ICommand;
import org.eclipse.graphiti.internal.command.MoveShapeFeatureCommandWithContext;
import org.eclipse.graphiti.internal.command.ResizeShapeFeatureCommandWithContext;
import org.eclipse.graphiti.internal.services.GraphitiInternal;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.AnchorContainer;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.ui.editor.DiagramBehavior;
import org.eclipse.graphiti.ui.internal.command.AddModelObjectCommand;
import org.eclipse.graphiti.ui.internal.command.CreateModelObjectCommand;
import org.eclipse.graphiti.ui.internal.command.GefCommandWrapper;
import org.eclipse.graphiti.ui.internal.command.MoveAnchorFeatureCommandWithContext;
import org.eclipse.graphiti.ui.internal.config.IConfigurationProviderInternal;
import org.eclipse.graphiti.ui.internal.contextbuttons.IContextButtonManager;
import org.eclipse.graphiti.ui.internal.parts.AdvancedAnchorEditPart;
import org.eclipse.graphiti.ui.internal.parts.ShapeEditPart;
import org.eclipse.graphiti.ui.internal.services.GraphitiUiInternal;
import org.eclipse.graphiti.ui.platform.IConfigurationProvider;
import org.eclipse.jface.viewers.ISelection;
/**
* An EditPolicy, where the Layout of the EditParts is important: they must have
* an XYLayout. It assumes, that this EditPart is a parent, whose children can
* be added/deleted/moved.
*
* @see org.eclipse.graphiti.ui.internal.policy.IEditPolicyFactory#createShapeXYLayoutEditPolicy()
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class ShapeXYLayoutEditPolicy extends XYLayoutEditPolicy {
private IConfigurationProviderInternal _configurationProvider;
/**
* Creates a new ShapeXYLayoutEditPolicy.
*
* @param configurationProvider
* The IConfigurationProviderInternal.
*/
protected ShapeXYLayoutEditPolicy(IConfigurationProviderInternal configurationProvider) {
_configurationProvider = configurationProvider;
}
protected final IConfigurationProviderInternal getConfigurationProvider() {
return _configurationProvider;
}
/**
* Is called, when a child EditPart shall be moved from another
* parent-EditPart into this parent-EditPart. It creates an
* ICommandCombiner.createSetParentReferenceCommand().
*
* @see org.eclipse.gef.editpolicies.ConstrainedLayoutEditPolicy#createAddCommand(org.eclipse.gef.EditPart,
* java.lang.Object)
*/
@Override
protected Command createAddCommand(EditPart child, Object constraint) {
Object model = child.getModel();
Rectangle rectangle = getHostFigure().getBounds();
if (model instanceof ConnectionDecorator && constraint instanceof Rectangle && rectangle != null) {
ICommand cmd = getMoveConnectionDecoratorCommand((ConnectionDecorator) model, (Rectangle) constraint,
rectangle.x, rectangle.y);
if (cmd != null) {
IConfigurationProvider configurationProvider = getConfigurationProvider();
return new GefCommandWrapper(cmd, configurationProvider.getDiagramBehavior().getEditingDomain());
}
}
return null;
}
@Override
protected EditPolicy createChildEditPolicy(EditPart child) {
if (child instanceof AdvancedAnchorEditPart) {
return new GFResizableEditPolicy(getConfigurationProvider());
}
if (!(child instanceof ShapeEditPart)) {
return new NonResizableEditPolicy();
}
PictogramElement pictogramElement = ((ShapeEditPart) child).getPictogramElement();
Shape shape = (Shape) pictogramElement;
ResizeShapeContext resizeShapeContext = new ResizeShapeContext(shape);
return new GFResizableEditPolicy(getConfigurationProvider(), resizeShapeContext);
}
/**
* Is called, when a child EditPart shall be moved inside this
* parent-EditPart (resized or changed XY-position). It creates an
* ICommandFactory.createChangeModelObjectConstraintCommand(().
*
* @see org.eclipse.gef.editpolicies.ConstrainedLayoutEditPolicy#createChangeConstraintCommand(org.eclipse.gef.EditPart,
* java.lang.Object)
*/
@Override
protected Command createChangeConstraintCommand(ChangeBoundsRequest request, EditPart child, Object constraint) {
IConfigurationProvider configurationProvider = getConfigurationProvider();
IFeatureProvider featureProvider = configurationProvider.getDiagramTypeProvider().getFeatureProvider();
CommandContainer ret = new CommandContainer(featureProvider);
Object model = child.getModel();
if ((!(model instanceof EObject) || GraphitiInternal.getEmfService().isObjectAlive((EObject) model))
&& (constraint instanceof Rectangle)) {
Rectangle rectangle = (Rectangle) constraint;
// connection decorators
if (model instanceof ConnectionDecorator) {
ICommand cmd = getMoveConnectionDecoratorCommand((ConnectionDecorator) model, rectangle, 0, 0);
if (cmd != null) {
ret.add(cmd);
}
} else
// anchors
if (model instanceof Anchor) {
Anchor anchor = (Anchor) model;
AnchorContainer anchorContainer = anchor.getParent();
IMoveAnchorContext context = createLayoutAnchorContext(anchor, anchorContainer, anchorContainer,
rectangle);
IMoveAnchorFeature moveAnchorFeature = featureProvider.getMoveAnchorFeature(context);
if (moveAnchorFeature != null) {
if (child instanceof GraphicalEditPart) {
ret.add(new MoveAnchorFeatureCommandWithContext(moveAnchorFeature, context,
(GraphicalEditPart) child));
}
}
} else
// shapes
if (model instanceof Shape) {
Shape shape = (Shape) model;
ContainerShape containerShape = shape.getContainer();
{
Rectangle rc = rectangle;
// modify the constraint in case if it is a Polyline
// graphics algorithm and the uppermost point is not at
// y==0 or the leftmost point is not at x==0
// see also Bug 383512
if (shape.getGraphicsAlgorithm() instanceof Polyline) {
Polyline polyline = (Polyline) shape.getGraphicsAlgorithm();
EList<org.eclipse.graphiti.mm.algorithms.styles.Point> points = polyline.getPoints();
if (points.size() > 0) {
org.eclipse.graphiti.mm.algorithms.styles.Point firstPoint = points.get(0);
int minX = firstPoint.getX();
int minY = firstPoint.getY();
for (org.eclipse.graphiti.mm.algorithms.styles.Point point : points) {
minX = Math.min(point.getX(), minX);
minY = Math.min(point.getY(), minY);
}
if (minX > 0 || minY > 0) {
rc = rectangle.getCopy();
if (minX > 0) {
rc.x -= minX;
}
if (minY > 0) {
rc.y -= minY;
}
}
}
}
IMoveShapeContext context = createMoveShapeContext(shape, containerShape, containerShape, rc);
IMoveShapeFeature moveShapeFeature = featureProvider.getMoveShapeFeature(context);
if (moveShapeFeature != null) {
if (child instanceof ShapeEditPart) {
// Check if size has changed. If yes we do a
// resize and no move. In this case do
// not add a move feature to the command because
// Move might not be allowed while
// Resize is allowed. Adding both Move and
// Resize leads to Resizing not possible.
if (!isDifferentSize(shape, rectangle)) {
// Not in resize
ret.add(new MoveShapeFeatureCommandWithContext(moveShapeFeature, context));
}
}
}
}
{
if (isDifferentSize(shape, rectangle)) {
IResizeShapeContext context = createResizeShapeContext(shape, rectangle,
request.getResizeDirection());
IResizeShapeFeature resizeShapeFeature = featureProvider.getResizeShapeFeature(context);
if (resizeShapeFeature != null) {
ret.add(new ResizeShapeFeatureCommandWithContext(resizeShapeFeature, context));
// } else if (child instanceof ShapeEditPart) {
// ret.add(new
// ResizeShapeFeatureCommandWithContext(resizeShapeFeature,
// context));
}
}
}
}
}
if (ret.containsCommands()) {
// hide context-buttons, if the user resizes/moves the shape
IContextButtonManager contextButtonManager = getConfigurationProvider().getContextButtonManager();
contextButtonManager.hideContextButtonsInstantly();
DiagramBehavior diagramBehavior = getConfigurationProvider().getDiagramBehavior();
return new GefCommandWrapper(ret, diagramBehavior.getEditingDomain());
} else {
return null;
}
}
/**
* @param constraint
* @param coll
* @param container
* @param container2
* @return
*/
protected IMoveShapeContext createMoveShapeContext(Shape shape, ContainerShape source, ContainerShape target,
Object constraint) {
MoveShapeContext ret = new MoveShapeContext(shape);
ret.setSourceContainer(source);
ret.setTargetContainer(target);
Point loc = null;
if (constraint instanceof Rectangle) {
Rectangle rect = (Rectangle) constraint;
loc = rect.getLocation();
} else if (constraint instanceof Point) {
loc = (Point) constraint;
}
if (loc != null) {
ret.setX(loc.x);
ret.setY(loc.y);
// calculate and store deltas
if (shape != null) {
GraphicsAlgorithm graphicsAlgorithm = shape.getGraphicsAlgorithm();
if (graphicsAlgorithm != null) {
ret.setDeltaX(loc.x - graphicsAlgorithm.getX());
ret.setDeltaY(loc.y - graphicsAlgorithm.getY());
}
}
}
return ret;
}
protected IResizeShapeContext createResizeShapeContext(Shape shape, Object constraint, int resizeDirection) {
ResizeShapeContext ret = new ResizeShapeContext(shape);
Point loc = null;
Dimension dim = null;
if (constraint instanceof Rectangle) {
Rectangle rect = (Rectangle) constraint;
dim = rect.getSize();
loc = rect.getLocation();
} else if (constraint instanceof Dimension) {
dim = (Dimension) constraint;
}
if (dim != null) {
ret.setWidth(dim.width);
ret.setHeight(dim.height);
}
if (loc != null) {
ret.setX(loc.x);
ret.setY(loc.y);
}
int direction = 0;
switch (resizeDirection) {
case PositionConstants.NORTH:
direction = IResizeShapeContext.DIRECTION_NORTH;
break;
case PositionConstants.SOUTH:
direction = IResizeShapeContext.DIRECTION_SOUTH;
break;
case PositionConstants.WEST:
direction = IResizeShapeContext.DIRECTION_WEST;
break;
case PositionConstants.EAST:
direction = IResizeShapeContext.DIRECTION_EAST;
break;
case PositionConstants.NORTH_WEST:
direction = IResizeShapeContext.DIRECTION_NORTH_WEST;
break;
case PositionConstants.NORTH_EAST:
direction = IResizeShapeContext.DIRECTION_NORTH_EAST;
break;
case PositionConstants.SOUTH_WEST:
direction = IResizeShapeContext.DIRECTION_SOUTH_WEST;
break;
case PositionConstants.SOUTH_EAST:
direction = IResizeShapeContext.DIRECTION_SOUTH_EAST;
break;
}
ret.setDirection(direction);
return ret;
}
protected IMoveAnchorContext createLayoutAnchorContext(Anchor shape, AnchorContainer source,
AnchorContainer target, Object constraint) {
AreaAnchorContext ret = new AreaAnchorContext(shape);
ret.setSourceContainer(source);
ret.setTargetContainer(target);
if (constraint instanceof Rectangle) {
Rectangle rect = (Rectangle) constraint;
ret.setX(rect.x);
ret.setY(rect.y);
ret.setWidth(rect.width);
ret.setHeight(rect.height);
}
return ret;
}
/**
* @param constraint
* @param coll
* @param container
* @param container2
* @return
*/
public static ICreateContext createCreateContext(ContainerShape target, Rectangle rect) {
CreateContext ret = new CreateContext();
ret.setTargetContainer(target);
ret.setX(rect.x);
ret.setY(rect.y);
ret.setWidth(rect.width);
ret.setHeight(rect.height);
return ret;
}
/**
* Is called, when a new child EditPart shall be created inside this
* parent-EditPart (with the CreationTool). It creates an
* ICommandCombiner.createCreateModelObjectCommand().
*
* @see org.eclipse.gef.editpolicies.LayoutEditPolicy#getCreateCommand(org.eclipse.gef.requests.CreateRequest)
*/
@Override
protected Command getCreateCommand(CreateRequest request) {
Command cmd = UnexecutableCommand.INSTANCE;
// check if _target is valid
Object parentObject = getHost().getModel();
if (!(parentObject instanceof ContainerShape))
return cmd;
Object createdObject = request.getNewObject();
// determine constraint
Rectangle rectangle = null;
if (request.getLocation() != null) {
rectangle = (Rectangle) getConstraintFor(request);
}
if (request.getNewObjectType() == ICreateFeature.class) {
ICreateContext context = createCreateContext((ContainerShape) parentObject, rectangle);
updateCreateContext((CreateContext) context);
ICreateFeature createFeature = (ICreateFeature) createdObject;
cmd = new CreateModelObjectCommand(getConfigurationProvider(), createFeature, context);
cmd.setLabel(createFeature.getDescription());
} else if (request.getNewObjectType() == ISelection.class) {
cmd = new AddModelObjectCommand(getConfigurationProvider(), (ContainerShape) parentObject,
(ISelection) createdObject, rectangle);
}
return cmd;
}
/**
* Usage unknown, returns null.
*
* @see org.eclipse.gef.editpolicies.LayoutEditPolicy#getDeleteDependantCommand(org.eclipse.gef.Request)
*/
@Override
protected Command getDeleteDependantCommand(Request request) {
return null;
}
private ICommand getMoveConnectionDecoratorCommand(ConnectionDecorator decorator, Rectangle constraint,
int offsetX, int offsetY) {
ICommand ret = null;
int x = constraint.x + offsetX;
int y = constraint.y + offsetY;
Connection connection = decorator.getConnection();
double location = decorator.getLocation();
if (decorator.isLocationRelative()) {
Point connectionMidpoint = GraphitiUiInternal.getGefService().getConnectionPointAt(connection, location);
if (connectionMidpoint != null) {
x = x - connectionMidpoint.x;
y = y - connectionMidpoint.y;
}
} else {
// absoluteLocation
Point absolutePointOnConnection = GraphitiUiInternal.getGefService().getAbsolutePointOnConnection(
connection, location);
if (absolutePointOnConnection != null) {
x = x - absolutePointOnConnection.x;
y = y - absolutePointOnConnection.y;
}
}
/**
* allow move of connection decorator only, if both connection ends are
* not in the selection of moved objects
*/
boolean isExecuteAllowed = true;
PictogramElement[] selectedPictogramElements = getConfigurationProvider().getDiagramBehavior()
.getSelectedPictogramElements();
List<PictogramElement> pes = Arrays.asList(selectedPictogramElements);
if (pes.size() > 1) {
PictogramElement startAnchorContainer = Graphiti.getPeService().getActiveContainerPe(
decorator.getConnection().getStart());
PictogramElement endAnchorContainer = Graphiti.getPeService().getActiveContainerPe(
decorator.getConnection().getEnd());
if (pes.contains(startAnchorContainer) || pes.contains(endAnchorContainer)) {
isExecuteAllowed = false;
}
}
IMoveConnectionDecoratorContext context = new MoveConnectionDecoratorContext(decorator, x, y, isExecuteAllowed);
IMoveConnectionDecoratorFeature feature = getFeatureProvider().getMoveConnectionDecoratorFeature(context);
if (feature != null) {
ret = new GenericFeatureCommandWithContext(feature, context);
}
return ret;
}
private IFeatureProvider getFeatureProvider() {
return getConfigurationProvider().getFeatureProvider();
}
/**
* Checks if the given shape and the given rectangle are different in size
*
* @param shape
* @param constraint
* @return
*/
private boolean isDifferentSize(Shape shape, Rectangle constraint) {
Rectangle rect = constraint;
IDimension sizeOfGA = Graphiti.getGaService().calculateSize(shape.getGraphicsAlgorithm(), false);
return rect.width != sizeOfGA.getWidth() || rect.height != sizeOfGA.getHeight();
}
private void updateCreateContext(CreateContext ctx) {
GraphicalViewer viewer = getConfigurationProvider().getDiagramContainer().getGraphicalViewer();
boolean gridVisible = (Boolean) viewer.getProperty(SnapToGrid.PROPERTY_GRID_VISIBLE);
boolean snapToGrid = (Boolean) viewer.getProperty(SnapToGrid.PROPERTY_GRID_ENABLED);
if (gridVisible && snapToGrid) {
Dimension dimension = (Dimension) viewer.getProperty(SnapToGrid.PROPERTY_GRID_SPACING);
if (dimension.width != 0 && dimension.height != 0) {
int snappedX = getSnapValue(ctx.getX(), dimension.width);
int snappedY = getSnapValue(ctx.getY(), dimension.height);
ctx.setX(snappedX);
ctx.setY(snappedY);
}
}
}
private int getSnapValue(int currentValue, int gridUnit) {
int units = currentValue / gridUnit;
if ((currentValue % gridUnit) > (gridUnit / 2)) {
units++;
}
return gridUnit * units;
}
}