/*****************************************************************************
* Copyright (c) 2010 CEA
*
*
* 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:
* Atos Origin - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.papyrus.uml.diagram.sequence.edit.policies;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Polyline;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.requests.CreateConnectionRequest;
import org.eclipse.gef.requests.ReconnectRequest;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.GraphicalNodeEditPolicy;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateConnectionViewAndElementRequest;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateConnectionViewAndElementRequest.ConnectionViewAndElementDescriptor;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateConnectionViewRequest;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateUnspecifiedTypeConnectionRequest;
import org.eclipse.gmf.runtime.emf.type.core.IElementType;
import org.eclipse.gmf.runtime.emf.type.core.IHintedType;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.papyrus.uml.diagram.common.service.AspectUnspecifiedTypeConnectionTool.CreateAspectUnspecifiedTypeConnectionRequest;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.CombinedFragment2EditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.CombinedFragmentEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.InteractionEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.InteractionOperandEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.LifelineEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.Message2EditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.Message3EditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.Message4EditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.Message5EditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.Message6EditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.Message7EditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.MessageEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.part.Messages;
import org.eclipse.papyrus.uml.diagram.sequence.part.UMLVisualIDRegistry;
import org.eclipse.papyrus.uml.diagram.sequence.providers.UMLElementTypes;
import org.eclipse.papyrus.uml.diagram.sequence.util.LifelineMessageCreateHelper;
import org.eclipse.papyrus.uml.diagram.sequence.util.SequenceRequestConstant;
import org.eclipse.papyrus.uml.diagram.sequence.util.SequenceUtil;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.OccurrenceSpecification;
/**
* A specific policy to handle the message :
* - Message cannot be uphill.
* - Message Occurrence Specification which are created along the message may have a different container than the message.
* - Message feedback on creation is always drawn in black (to avoid invisible feedback)
*
*/
@SuppressWarnings({ "restriction", "unchecked", "rawtypes" })
public class SequenceGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {
/** A static margin to allow drawing of straight message */
private static final int MARGIN = 2;
/**
* Gets the command to start the creation of a new connection and relationship. This will update the request appropriately. Also completes the
* request with nearby occurrence specification information.
*
* @param request
* creation request
* @return Command
*/
@Override
protected Command getConnectionAndRelationshipCreateCommand(CreateConnectionViewAndElementRequest request) {
Map<String, Object> extendedData = request.getExtendedData();
// record the nearest event if necessary
String requestHint = request.getConnectionViewAndElementDescriptor().getSemanticHint();
if(isCreatedNearOccurrenceSpecification(requestHint)) {
LifelineEditPart lifelinePart = SequenceUtil.getParentLifelinePart(getHost());
if(lifelinePart != null) {
Entry<Point, List<OccurrenceSpecification>> eventAndLocation = SequenceUtil.findNearestEvent(request.getLocation(), lifelinePart);
// find an event near enough to create the constraint or observation
List<OccurrenceSpecification> events = Collections.emptyList();
Point location = null;
if(eventAndLocation != null) {
location = eventAndLocation.getKey();
events = eventAndLocation.getValue();
}
extendedData.put(SequenceRequestConstant.NEAREST_OCCURRENCE_SPECIFICATION, events);
extendedData.put(SequenceRequestConstant.OCCURRENCE_SPECIFICATION_LOCATION, location);
if(location != null) {
request.setLocation(location);
}
}
}
return super.getConnectionAndRelationshipCreateCommand(request);
}
/**
* Gets the command to complete the creation of a new connection and relationship. Also completes the request with nearby occurrence specification
* information.
*
* @param request
* the creation request
* @return Command
*/
@Override
protected Command getConnectionAndRelationshipCompleteCommand(CreateConnectionViewAndElementRequest request) {
Map<String, Object> extendedData = request.getExtendedData();
// record the nearest event if necessary
String requestHint = request.getConnectionViewAndElementDescriptor().getSemanticHint();
if(isCreatedNearOccurrenceSpecification(requestHint)) {
LifelineEditPart lifelinePart = SequenceUtil.getParentLifelinePart(getHost());
if(lifelinePart != null) {
Entry<Point, List<OccurrenceSpecification>> eventAndLocation = SequenceUtil.findNearestEvent(request.getLocation(), lifelinePart);
// find an event near enough to create the constraint or observation
List<OccurrenceSpecification> events = Collections.emptyList();
Point location = null;
if(eventAndLocation != null) {
location = eventAndLocation.getKey();
events = eventAndLocation.getValue();
}
extendedData.put(SequenceRequestConstant.NEAREST_OCCURRENCE_SPECIFICATION_2, events);
extendedData.put(SequenceRequestConstant.OCCURRENCE_SPECIFICATION_LOCATION_2, location);
if(location != null) {
request.setLocation(location);
}
}
}
return super.getConnectionAndRelationshipCompleteCommand(request);
}
/**
* Return true if creation must be performed to or from an occurrence specification
*
* @param requestHint
* the hint of object to create
* @return true if creation to or from an occurrence specification
*/
private boolean isCreatedNearOccurrenceSpecification(String requestHint) {
String generalOrderingHint = ((IHintedType)UMLElementTypes.GeneralOrdering_4012).getSemanticHint();
return generalOrderingHint.equals(requestHint);
}
/**
* Return true if a message is being created
*
* @param requestHint
* the hint of object to create
* @return true if creation of a message
*/
private boolean isMessageHint(String requestHint) {
List<String> messageHints = new ArrayList<String>(7);
String messageHint = ((IHintedType)UMLElementTypes.Message_4003).getSemanticHint();
messageHints.add(messageHint);
messageHint = ((IHintedType)UMLElementTypes.Message_4004).getSemanticHint();
messageHints.add(messageHint);
messageHint = ((IHintedType)UMLElementTypes.Message_4005).getSemanticHint();
messageHints.add(messageHint);
messageHint = ((IHintedType)UMLElementTypes.Message_4006).getSemanticHint();
messageHints.add(messageHint);
messageHint = ((IHintedType)UMLElementTypes.Message_4007).getSemanticHint();
messageHints.add(messageHint);
messageHint = ((IHintedType)UMLElementTypes.Message_4008).getSemanticHint();
messageHints.add(messageHint);
messageHint = ((IHintedType)UMLElementTypes.Message_4009).getSemanticHint();
messageHints.add(messageHint);
return messageHints.contains(requestHint);
}
@Override
protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
// move source point to the bottom if it is contained in LifelineNameContainerFigure
EditPart targetEditPart = request.getTargetEditPart();
if (targetEditPart != null
&& targetEditPart instanceof LifelineEditPart) {
LifelineEditPart target = (LifelineEditPart) targetEditPart;
if (target.getPrimaryShape() != null) {
Rectangle sourceBounds = target.getPrimaryShape()
.getFigureLifelineNameContainerFigure().getBounds()
.getCopy();
Point sourcePointCopy = request.getLocation().getCopy();
target.getFigure().translateToRelative(sourcePointCopy);
if (sourcePointCopy.y() < sourceBounds.getBottom().y()) {
target.getFigure().translateToAbsolute(sourceBounds);
request.getLocation()
.setY(sourceBounds.getBottom().y()+1);
}
}
}
request.getExtendedData().put(
SequenceRequestConstant.SOURCE_LOCATION_DATA,
request.getLocation());
return super.getConnectionCreateCommand(request);
}
/**
* Overrides to disable uphill message
*/
@Override
protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {
Command command = super.getConnectionCompleteCommand(request);
if(command == null) {
return UnexecutableCommand.INSTANCE;
}
// disable the following code if we are not creating a message.
String requestHint = null;
if(request instanceof CreateConnectionViewRequest) {
requestHint = ((CreateConnectionViewRequest)request).getConnectionViewDescriptor().getSemanticHint();
if(!isMessageHint(requestHint)) {
return command;
}
}
// ICommandProxy proxy = (ICommandProxy)request.getStartCommand();
// CompositeCommand cc = (CompositeCommand)proxy.getICommand();
// Iterator<?> commandItr = cc.iterator();
// while(commandItr.hasNext()) {
// Object obj = commandItr.next();
// if(obj instanceof SetConnectionBendpointsCommand) {
// SetConnectionBendpointsCommand sbbCommand = (SetConnectionBendpointsCommand)obj;
//final PointList pointList = sbbCommand.getNewPointList();
Point sourcePoint = (Point)request.getExtendedData().get(SequenceRequestConstant.SOURCE_LOCATION_DATA);
Point targetPoint = request.getLocation();
// prevent uphill message (leave margin for horizontal messages)
boolean messageCreate =((IHintedType)UMLElementTypes.Message_4006).getSemanticHint().equals(requestHint);
if(sourcePoint == null || sourcePoint.y >= targetPoint.y + MARGIN) {
if(!messageCreate && !isLostFoundMessage(requestHint))
return UnexecutableCommand.INSTANCE;
}
// prevent uphill message (for self recursive message)
if(request.getSourceEditPart().equals(request.getTargetEditPart()) && sourcePoint.y >= targetPoint.y) {
return UnexecutableCommand.INSTANCE;
}
// prevent uphill message (for reply message)
if(request instanceof CreateConnectionViewAndElementRequest) {
ConnectionViewAndElementDescriptor desc = ((CreateConnectionViewAndElementRequest)request).getConnectionViewAndElementDescriptor();
String replyHint = ((IHintedType)UMLElementTypes.Message_4005).getSemanticHint();
if(replyHint.equals(desc.getSemanticHint()) && request.getSourceEditPart() instanceof IGraphicalEditPart) {
Rectangle srcBounds = SequenceUtil.getAbsoluteBounds((IGraphicalEditPart)request.getSourceEditPart());
int bottom = srcBounds.getBottom().y;
if(bottom >= targetPoint.y + MARGIN) {
return UnexecutableCommand.INSTANCE;
}
}
}
// prevent duplicate create link
if( messageCreate && request.getSourceEditPart() != null && request.getTargetEditPart() != null){
if(LifelineMessageCreateHelper.hasMessageCreate((GraphicalEditPart)request.getSourceEditPart(), request.getTargetEditPart()))
return UnexecutableCommand.INSTANCE;
}
request.getExtendedData().put(SequenceRequestConstant.SOURCE_MODEL_CONTAINER, SequenceUtil.findInteractionFragmentContainerAt(sourcePoint, getHost()));
request.getExtendedData().put(SequenceRequestConstant.TARGET_MODEL_CONTAINER, SequenceUtil.findInteractionFragmentContainerAt(targetPoint, getHost()));
// In case we are creating a connection to/from a CoRegion, we will need the lifeline element where is drawn the CoRegion later in the process.
EditPart targetEditPart = getTargetEditPart(request);
if(targetEditPart instanceof CombinedFragment2EditPart) {
request.getExtendedData().put(SequenceRequestConstant.LIFELINE_GRAPHICAL_CONTAINER, ((CombinedFragment2EditPart)targetEditPart).getAttachedLifeline());
}
// change constraint of the target lifeline after added a Create Message
if (request.getTargetEditPart() instanceof LifelineEditPart
&& !(request.getSourceEditPart().equals(request
.getTargetEditPart()))) {
if (requestHint.equals((((IHintedType) UMLElementTypes.Message_4006).getSemanticHint()))) {
LifelineEditPart target = (LifelineEditPart) request
.getTargetEditPart();
command = LifelineMessageCreateHelper.moveLifelineDown(command, target, sourcePoint.getCopy());
}
}
return command;
}
/**
* Override to disable uphill message
*/
@Override
protected Command getReconnectSourceCommand(ReconnectRequest request) {
if(isUphillMessage(request) && !isLostFoundMessage(request)) {
return UnexecutableCommand.INSTANCE;
}
// prevent duplicate link
if(request.getConnectionEditPart() instanceof Message4EditPart && request.getTarget() != null && !LifelineMessageCreateHelper.canReconnectMessageCreate(request)){
return UnexecutableCommand.INSTANCE;
}
return super.getReconnectSourceCommand(request);
}
private boolean isLostFoundMessage(ReconnectRequest request) {
ConnectionEditPart connectionEditPart = request.getConnectionEditPart();
if(connectionEditPart instanceof Message7EditPart || connectionEditPart instanceof Message6EditPart )
return true;
return false;
}
private boolean isLostFoundMessage(String requestHint) {
if(((IHintedType)UMLElementTypes.Message_4008).getSemanticHint().equals(requestHint) || ((IHintedType)UMLElementTypes.Message_4009).getSemanticHint().equals(requestHint))
return true;
return false;
}
/**
* Override to disable uphill message
*/
@Override
protected Command getReconnectTargetCommand(ReconnectRequest request) {
if(isUphillMessage(request) && !isLostFoundMessage(request)) {
return UnexecutableCommand.INSTANCE;
}
// prevent duplicate link
if(request.getConnectionEditPart() instanceof Message4EditPart && request.getTarget() != null && !LifelineMessageCreateHelper.canReconnectMessageCreate(request)){
return UnexecutableCommand.INSTANCE;
}
return super.getReconnectTargetCommand(request);
}
/**
* Check that a message doesn't have its target point above its source point
*
* @param request
* the ReconnectRequest
* @return true if the target point is above the source point
*/
protected boolean isUphillMessage(ReconnectRequest request) {
ConnectionEditPart connectionEditPart = request.getConnectionEditPart();
if(connectionEditPart.getModel() instanceof Edge) {
Edge edge = (Edge)connectionEditPart.getModel();
if(edge.getElement() instanceof Message) {
if(connectionEditPart.getFigure() instanceof Polyline) {
// get connection end points translated to absolute
Polyline polyline = (Polyline)connectionEditPart.getFigure();
Point end = polyline.getEnd().getCopy();
Point start = polyline.getStart().getCopy();
polyline.getParent().translateToAbsolute(end);
polyline.getParent().translateToAbsolute(start);
// look at the request rather than at both polyline ends which may not be up to date
if(REQ_RECONNECT_SOURCE.equals(request.getType())) {
return request.getLocation().y >= end.y + MARGIN;
} else if(REQ_RECONNECT_TARGET.equals(request.getType())) {
return start.y >= request.getLocation().y + MARGIN;
} else {
return start.y >= end.y + MARGIN;
}
}
}
}
return false;
}
/**
* Overrides to set the color of the dummyConnection to color black.
* This allow to see the feedback of the connection when it is created.
* By default, the color was the foreground color of the lifeline, which is always blank leading to an invisible feedback.
*
*/
@Override
protected Connection createDummyConnection(Request req) {
Connection conn = super.createDummyConnection(req);
conn.setForegroundColor(org.eclipse.draw2d.ColorConstants.black);
return conn;
}
/**
* Gets a command that pops up a menu which allows the user to select which
* type of connection to be created and then creates the connection.
*
* @param content
* The list of items making up the content of the popup menu.
* @param request
* The relevant create connection request.
* @return the command to popup up the menu and create the connection
*/
protected ICommand getPromptAndCreateConnectionCommand(List content, CreateConnectionRequest request) {
return new SequencePromptAndCreateConnectionCommand(content, request);
}
/**
* Extends {@link PromptAndCreateConnectionCommand} to specify the type of message that can be selected.
*/
protected class SequencePromptAndCreateConnectionCommand extends PromptAndCreateConnectionCommand {
/**
* @see {@link PromptAndCreateConnectionCommand#PromptAndCreateConnectionCommand(List, CreateConnectionRequest)}
*/
public SequencePromptAndCreateConnectionCommand(List content, CreateConnectionRequest request) {
super(content, request);
}
/**
* Defines a specific label provider to handle message.
*/
@Override
protected ILabelProvider getLabelProvider() {
return new LabelProvider() {
@Override
public String getText(Object object) {
if(object instanceof IHintedType) {
IHintedType elementType = (IHintedType)object;
switch(UMLVisualIDRegistry.getVisualID(elementType.getSemanticHint())) {
case MessageEditPart.VISUAL_ID:
return Messages.createMessageSync1CreationTool_title;
case Message2EditPart.VISUAL_ID:
return Messages.createMessageAsync2CreationTool_title;
case Message3EditPart.VISUAL_ID:
return Messages.createMessageReply3CreationTool_title;
case Message4EditPart.VISUAL_ID:
return Messages.createMessageCreate4CreationTool_title;
case Message5EditPart.VISUAL_ID:
return Messages.createMessageDelete5CreationTool_title;
case Message6EditPart.VISUAL_ID:
return Messages.createMessageLost6CreationTool_title;
case Message7EditPart.VISUAL_ID:
return Messages.createMessageFound7CreationTool_title;
}
}
return super.getText(object);
}
};
}
}
public EditPart getTargetEditPart(Request request) {
if (REQ_CONNECTION_START.equals(request.getType())
|| REQ_CONNECTION_END.equals(request.getType())
|| REQ_RECONNECT_SOURCE.equals(request.getType())
|| REQ_RECONNECT_TARGET.equals(request.getType())){
EditPart host = getHost();
if(request instanceof CreateConnectionRequest){
if(host instanceof InteractionEditPart){
if(REQ_CONNECTION_END.equals(request.getType()) && isCreateConnectionRequest(request,UMLElementTypes.Message_4008 )){
return host;
}
if(REQ_CONNECTION_START.equals(request.getType()) && isCreateConnectionRequest(request,UMLElementTypes.Message_4009 )){
return host;
}
InteractionEditPart interactionPart = (InteractionEditPart)host;
CreateConnectionRequest req = (CreateConnectionRequest)request;
IFigure figure = interactionPart.getFigure();
Point location = req.getLocation().getCopy();
figure.translateToRelative(location);
// if mouse location is far from border, do not handle connection event
Rectangle innerRetangle = figure.getBounds().getCopy().shrink(20, 20);
if(innerRetangle.contains(location)){
return null;
}
}else if(host instanceof InteractionOperandEditPart){
return null;
}else if(host instanceof CombinedFragmentEditPart){
CreateConnectionRequest req = (CreateConnectionRequest)request;
IFigure figure = ((GraphicalEditPart)host).getFigure();
Point location = req.getLocation().getCopy();
figure.translateToRelative(location);
// if mouse location is far from border, do not handle connection event
Rectangle innerRetangle = figure.getBounds().getCopy().shrink(10, 10);
if(innerRetangle.contains(location)){
return null;
}
}
}
return host;
}
return null;
}
protected boolean isCreateConnectionRequest(Request request, IElementType type) {
if( request instanceof CreateAspectUnspecifiedTypeConnectionRequest){
List types = ((CreateUnspecifiedTypeConnectionRequest) request).getElementTypes();
if(types.contains(type)){
return true;
}
}
if(request instanceof CreateConnectionViewRequest ){
String requestHint = ((CreateConnectionViewRequest)request).getConnectionViewDescriptor().getSemanticHint();
if(((IHintedType)type).getSemanticHint().equals(requestHint))
return true;
}
return false;
}
}