/*******************************************************************************
* Copyright (c) 2009 Conselleria de Infraestructuras y Transporte, Generalitat
* de la Comunitat Valenciana . 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: Gabriel Merin Cubero (Prodevelop) – Sequence Diagram Implementation
*
******************************************************************************/
package org.eclipse.papyrus.uml.diagram.common.commands;
import java.util.List;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.Request;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeNodeEditPart;
import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.IdentityAnchor;
import org.eclipse.gmf.runtime.notation.View;
/**
* This class modifies the anchors of the edges connected to the passed element
* so that they can preserve their position after the resize of the figure. If
* any of the anchors does not fit in the new size, it will be positioned at the
* nearest bound.
*
* This class allows to preserve the position in the Y axis, in the X axis or in
* both axis.
*
* @author gmerin
*
*/
@SuppressWarnings("unchecked")
public class PreserveAnchorsPositionCommand extends AbstractTransactionalCommand {
// The Shape being resized
private ShapeNodeEditPart shapeEP;
// The size delta aplied to the shape
private Dimension sizeDelta;
private int preserveAxis;
//
private IFigure figure;
// The resize direction
private int resizeDirection;
// Command's label
protected final static String COMMAND_LABEL = "Modify Anchors to Preserve Position";
// Command's error message
protected final static String COMMAND_ERROR_MESSAGE = "One of the anchors is left outside of the new figure's size";
// Constants to describe which axis position should be preserved
public final static int PRESERVE_Y = 0;
public final static int PRESERVE_X = 1;
public final static int PRESERVE_XY = 2;
/**
* Constructor. It needs the shape being resized, it's re-size delta and the
* axis where the position should be preserved. The different preserveAxis
* values are the following:
* <ul>
* <li>ModifyAnchorsToPreservePosition.PRESERVE_Y</li>
* <li>ModifyAnchorsToPreservePosition.PRESERVE_X</li>
* <li>ModifyAnchorsToPreservePosition.PRESERVE_XY</li>
* </ul>
*
* @param shapeEP
* the ShapeNodeEditPart that is being resized
* @param sizeDelta
* the re-size delta
* @param preserveAxis
* the axis where the position should be preserved. If the given
* value is not valid, then PRESERVE_Y will be taken as default
*/
public PreserveAnchorsPositionCommand(ShapeNodeEditPart shapeEP, Dimension sizeDelta, int preserveAxis) {
super(shapeEP.getEditingDomain(), COMMAND_LABEL, null);
setShapeEP(shapeEP);
setSizeDelta(sizeDelta);
setPreserveAxis(preserveAxis);
}
/**
* Constructor. It needs the shape being resized, it's re-size delta and the
* axis where the position should be preserved. The different preserveAxis
* values are the following:
* <ul>
* <li>ModifyAnchorsToPreservePosition.PRESERVE_Y</li>
* <li>ModifyAnchorsToPreservePosition.PRESERVE_X</li>
* <li>ModifyAnchorsToPreservePosition.PRESERVE_XY</li>
* </ul>
*
*
* @param shapeEP
* the ShapeNodeEditPart that is being resized
* @param sizeDelta
* the re-size delta
* @param preserveAxis
* the axis where the position should be preserved. If the given
* value is not valid, then PRESERVE_Y will be taken as default
* @param figure
* the figure where the anchors are (when it is not the
* getShapeEP().getFigure()).
* @param resizeDirection
* the resize direction. Possible values are
* <ul>
* <li>{@link org.eclipse.draw2d.PositionConstants#EAST}
* <li>{@link org.eclipse.draw2d.PositionConstants#WEST}
* <li>{@link org.eclipse.draw2d.PositionConstants#NORTH}
* <li>{@link org.eclipse.draw2d.PositionConstants#SOUTH}
* <li>{@link org.eclipse.draw2d.PositionConstants#NORTH_EAST}
* <li>{@link org.eclipse.draw2d.PositionConstants#NORTH_WEST}
* <li>{@link org.eclipse.draw2d.PositionConstants#SOUTH_EAST}
* <li>{@link org.eclipse.draw2d.PositionConstants#SOUTH_WEST}
* </ul>
*/
public PreserveAnchorsPositionCommand(ShapeNodeEditPart shapeEP, Dimension sizeDelta, int preserveAxis, IFigure figure, int resizeDirection) {
this(shapeEP, sizeDelta, preserveAxis);
this.figure = figure;
this.resizeDirection = resizeDirection;
}
/**
* Set the new value of the preserveAxis property
*
* @param preserveAxis
* the new preserveAxis value
*/
public void setPreserveAxis(int preserveAxis) {
if(preserveAxis != PRESERVE_Y && preserveAxis != PRESERVE_X && preserveAxis != PRESERVE_XY) {
this.preserveAxis = PRESERVE_Y;
} else {
this.preserveAxis = preserveAxis;
}
}
/**
* Return the current value of the preserveAxis property
*
* @return preserveAxis current value
*/
public int getPreserveAxis() {
return preserveAxis;
}
/**
* Set the new value of the ShapeNodeEditPart property
*
* @param shapeEP
*/
public void setShapeEP(ShapeNodeEditPart shapeEP) {
this.shapeEP = shapeEP;
}
/**
* Return the current value of the ShapeNodeEditPart property
*
* @return shapeEP
*/
public ShapeNodeEditPart getShapeEP() {
return shapeEP;
}
/**
* apex updated
*
* Return the bounds of the ShapeNodeEditPart's figure
*
* @return The bounds
*/
public Rectangle getFigureBounds() {
/* apex improved start */
Rectangle rect = null;
if (figure != null) {
rect = figure.getBounds().getCopy();
}
else {
rect = getShapeEP().getFigure().getBounds().getCopy();
}
figure.translateToAbsolute(rect);
return rect;
/* apex improved end */
/* apex replaced
if(figure != null) {
return figure.getBounds();
}
return getShapeEP().getFigure().getBounds();
*/
}
/**
* Return's the view associated with the ShapeNodeEditPart
*
* @return The View
*/
public View getView() {
return (View)getShapeEP().getModel();
}
/**
* Sets the new size delta property
*
* @param sizeDelta
* the new sizeDelta value
*/
protected void setSizeDelta(Dimension sizeDelta) {
this.sizeDelta = sizeDelta;
}
/**
* Returns the current size delta property
*
* @return The size delta
*/
public Dimension getSizeDelta() {
return sizeDelta;
}
/* apex added start*/
public int getResizeDirection() {
return resizeDirection;
}
/* apex added end */
/**
* Execution of the command
*/
@Override
protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
View view = getView();
List<Edge> sourceList = ViewUtil.getSourceConnections(view);
List<Edge> targetList = ViewUtil.getTargetConnections(view);
for(Edge edge : sourceList) {
IdentityAnchor anchor = (IdentityAnchor)edge.getSourceAnchor();
if(anchor != null) {
anchor.setId(getNewIdStr(anchor));
}
}
for(Edge edge : targetList) {
IdentityAnchor anchor = (IdentityAnchor)edge.getTargetAnchor();
if(anchor != null) {
anchor.setId(getNewIdStr(anchor));
}
}
return CommandResult.newOKCommandResult();
}
/**
* Returns the new anchor's position to preserve it's position after
*
* @param anchor
* @return the new IdStr
*/
protected String getNewIdStr(IdentityAnchor anchor) {
Dimension sizeDelta = getSizeDelta();
Rectangle figureBounds = getFigureBounds();
PrecisionPoint pp = BaseSlidableAnchor.parseTerminalString(anchor.getId());
if(getPreserveAxis() == PRESERVE_Y || getPreserveAxis() == PRESERVE_XY) {
int anchorYPos = (int)Math.round(figureBounds.height * pp.preciseY);
pp.preciseY = (double)anchorYPos / (figureBounds.height + sizeDelta.height);
// If the resize direction is NORTH, the location of the figure
// move, but the anchor stay visually at the same location
if(PositionConstants.NORTH == resizeDirection || PositionConstants.NORTH_EAST == resizeDirection || PositionConstants.NORTH_WEST == resizeDirection) {
pp.preciseY = pp.preciseY + ((double)sizeDelta.height / (figureBounds.height + sizeDelta.height));
}
if(pp.preciseY > 1.0) {
pp.preciseY = 1.0;
} else if(pp.preciseY < 0.0) {
pp.preciseY = 0.0;
}
}
if(getPreserveAxis() == PRESERVE_X || getPreserveAxis() == PRESERVE_XY) {
int anchorXPos = (int)Math.round(figureBounds.width * pp.preciseX);
pp.preciseX = (double)anchorXPos / (figureBounds.width + sizeDelta.width);
// If the resize direction is WEST, the location of the figure move,
// but the anchor stay visually at the same location
if(PositionConstants.WEST == resizeDirection || PositionConstants.NORTH_WEST == resizeDirection || PositionConstants.SOUTH_WEST == resizeDirection) {
pp.preciseX = pp.preciseX + ((double)sizeDelta.width / (figureBounds.width + sizeDelta.width));
}
if(pp.preciseX > 1.0) {
pp.preciseX = 1.0;
} else if(pp.preciseX < 0.0) {
pp.preciseX = 0.0;
}
}
String idStr = (new BaseSlidableAnchor(null, pp)).getTerminal();
return idStr;
}
/**
* This operation checks if, after resizing the ShapeNodeEditPart, all links
* anchors will fit inside the figure in case their positions are preserved
*
* @param shapeEP
* That shape being resized
* @param sizeDelta
* The SizeDelta for the resize
* @param preserveAxis
* The axisxxx
* @return The new SizeDelta to preserve anchors' positions
*/
public static Dimension getSizeDeltaToFitAnchors(ShapeNodeEditPart shapeEP, Dimension sizeDelta, int preserveAxis) {
Dimension newSizeDelta = new Dimension(sizeDelta);
View view = (View)shapeEP.getModel();
Rectangle figureBounds = shapeEP.getFigure().getBounds();
List<Edge> sourceList = ViewUtil.getSourceConnections(view);
List<Edge> targetList = ViewUtil.getTargetConnections(view);
for(Edge edge : sourceList) {
IdentityAnchor anchor = (IdentityAnchor)edge.getSourceAnchor();
modifySizeDeltaToFitAnchor(anchor, newSizeDelta, preserveAxis, figureBounds);
}
for(Edge edge : targetList) {
IdentityAnchor anchor = (IdentityAnchor)edge.getTargetAnchor();
modifySizeDeltaToFitAnchor(anchor, newSizeDelta, preserveAxis, figureBounds);
}
return newSizeDelta;
}
/**
* Used inside the getSizeDeltaToFitAnchors operation. It's goal is to
* modify a SizeDelta in order to keep fitting an anchor within the
* figureBounds
*
* @param anchor
* The anchor whose position will be kept
* @param sizeDelta
* @param preserveAxis
* @param figureBounds
*/
protected static void modifySizeDeltaToFitAnchor(IdentityAnchor anchor, Dimension sizeDelta, int preserveAxis, Rectangle figureBounds) {
if(anchor == null) {
return;
}
PrecisionPoint pp = BaseSlidableAnchor.parseTerminalString(anchor.getId());
int margin = 6;
if(preserveAxis == PRESERVE_Y || preserveAxis == PRESERVE_XY) {
int anchorYPos = (int)Math.round(figureBounds.height * pp.preciseY);
int newHeight = figureBounds.height + sizeDelta.height;
if(anchorYPos + margin > newHeight) {
sizeDelta.height = (anchorYPos - figureBounds.height) + margin;
}
}
if(preserveAxis == PRESERVE_X || preserveAxis == PRESERVE_XY) {
int anchorXPos = (int)Math.round(figureBounds.width * pp.preciseX);
int newWidth = figureBounds.width + sizeDelta.width;
if(anchorXPos + margin > newWidth) {
sizeDelta.width = (anchorXPos - figureBounds.width) + margin;
}
}
}
/**
* Creations of a new request in order to have a correct visualization of
* the feedback in order to preserve links's anchors.
*
* @param request
* @param editPart
* @return a replication of the request but with a SizeDelta modification
*/
// @unused
public static Request getNewSourceFeedbackRequest(Request request, ShapeNodeEditPart editPart) {
if(request instanceof ChangeBoundsRequest) {
ChangeBoundsRequest currRequest = (ChangeBoundsRequest)request;
Dimension oldDelta = currRequest.getSizeDelta();
Dimension newDelta = getSizeDeltaToFitAnchors(editPart, oldDelta, PreserveAnchorsPositionCommand.PRESERVE_Y);
// Information for creating a new ChangeBoundsRequest has been taken
// from org.eclipse.gef.editpolicies.ResizableEditPolicy
ChangeBoundsRequest newRequest = new ChangeBoundsRequest();
newRequest.setMoveDelta(currRequest.getMoveDelta());
newRequest.setSizeDelta(newDelta);
newRequest.setLocation(currRequest.getLocation());
newRequest.setExtendedData(currRequest.getExtendedData());
newRequest.setResizeDirection(currRequest.getResizeDirection());
newRequest.setType(currRequest.getType());
return newRequest;
} else {
return request;
}
}
}