| /******************************************************************************* |
| * <copyright> |
| * |
| * Copyright (c) 2005, 2016 SAP AG. |
| * 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: |
| * SAP AG - 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 |
| * |
| * </copyright> |
| * |
| *******************************************************************************/ |
| 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); |
| 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; |
| } |
| } |