/*****************************************************************************
* 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.service;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartListener;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.NodeEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gef.tools.AbstractConnectionCreationTool;
import org.eclipse.gef.tools.AbstractTool;
import org.eclipse.gef.tools.TargetingTool;
import org.eclipse.gmf.runtime.diagram.ui.editparts.CompartmentEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ConnectionNodeEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.gmf.runtime.emf.type.core.IElementType;
import org.eclipse.papyrus.uml.diagram.common.helper.DurationConstraintHelper;
import org.eclipse.papyrus.uml.diagram.common.service.AspectUnspecifiedTypeCreationTool;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.LifelineEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.util.SequenceRequestConstant;
import org.eclipse.papyrus.uml.diagram.sequence.util.SequenceUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.uml2.uml.OccurrenceSpecification;
public class DurationCreationTool extends AspectUnspecifiedTypeCreationTool {
static private Cursor CURSOR_TARGET_MENU = new Cursor(Display.getDefault(), SWT.CURSOR_HAND);
/**
* The state which indicates that the duration creation has begun. This means that the
* source of the duration has been identified, and the user is still to determine the
* target.
*/
protected static final int STATE_CONNECTION_STARTED = TargetingTool.MAX_STATE << 1;
/**
* The flag which indicates that feedback figure must be shown
*/
private static final int FLAG_SOURCE_FEEDBACK = TargetingTool.MAX_FLAG << 1;
/**
* The max state.
*/
protected static final int MAX_STATE = STATE_CONNECTION_STARTED;
/**
* The max flag.
*/
protected static final int MAX_FLAG = FLAG_SOURCE_FEEDBACK;
/**
* The first element constrained by the duration constraint
*/
private EditPart connectionSource;
/**
* The viewer
*/
private EditPartViewer viewer;
/**
* Listener for the edit part deactivation
*/
private EditPartListener.Stub deactivationListener = new EditPartListener.Stub() {
public void partDeactivated(EditPart editpart) {
handleSourceDeactivated();
}
};
/**
* Constructor of the creation tool.
*
* @param elementTypes
* element types the tool can create
*/
public DurationCreationTool(List<IElementType> elementTypes) {
super(elementTypes);
setDefaultCursor(SharedCursors.CURSOR_PLUG);
setDisabledCursor(SharedCursors.CURSOR_PLUG_NOT);
}
/**
* @see org.eclipse.gef.tools.AbstractTool#handleDrag()
*/
protected boolean handleDrag() {
if(isInState(STATE_CONNECTION_STARTED))
return handleMove();
return false;
}
/**
* @see org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
*/
protected boolean handleDragInProgress() {
if(isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS))
return handleMove();
return false;
}
/**
* @see org.eclipse.gef.tools.AbstractTool#getDebugNameForState(int)
*/
protected String getDebugNameForState(int s) {
if(s == STATE_CONNECTION_STARTED || s == STATE_ACCESSIBLE_DRAG_IN_PROGRESS)
return "Connection Started";//$NON-NLS-1$
return super.getDebugNameForState(s);
}
/**
* Sets the location of the request.
*
* @see org.eclipse.gef.tools.TargetingTool#updateTargetRequest()
*/
protected void updateTargetRequest() {
CreateRequest req = getCreateRequest();
req.setLocation(getLocation());
}
/**
* Sets the target request. This method is typically not called; subclasses normally override {@link #createTargetRequest()}.
*
* @see org.eclipse.gef.tools.TargetingTool#setTargetRequest(org.eclipse.gef.Request)
* @param req
* the target request
*/
@Override
protected void setTargetRequest(Request req) {
if(req == null && connectionSource != null) {
// do not erase the request when target changes (behavior inherited from CreationTool)
return;
}
super.setTargetRequest(req);
}
/**
* Called if the source editpart is deactivated for some reason during the creation
* process. For example, the user performs an Undo while in the middle of creating a
* connection, which undoes a prior command which created the source.
*/
protected void handleSourceDeactivated() {
setState(STATE_INVALID);
handleInvalidInput();
handleFinished();
}
/**
* Method that is called when the gesture to create the connection has been received.
* Subclasses may extend or override this method to do additional creation setup, such as
* prompting the user to choose an option about the connection being created. Returns <code>true</code> to indicate that the connection creation
* succeeded.
*
* @return <code>true</code> if the connection creation was performed
*/
protected boolean handleCreateConnection() {
eraseSourceFeedback();
Command endCommand = getCommand();
setCurrentCommand(endCommand);
executeCurrentCommand();
return true;
}
/**
* @see org.eclipse.gef.tools.TargetingTool#handleInvalidInput()
*/
protected boolean handleInvalidInput() {
eraseSourceFeedback();
setConnectionSource(null);
return super.handleInvalidInput();
}
/**
* When the button is first pressed, the source node and its command contribution are
* determined and locked in. After that time, the tool will be looking for the target
* node to complete the connection
*
* @see org.eclipse.gef.tools.AbstractTool#handleButtonDown(int)
* @param button
* which button is pressed
* @return <code>true</code> if the button down was processed
*/
protected boolean handleButtonDown(int button) {
if(button == 1 && stateTransition(STATE_CONNECTION_STARTED, STATE_TERMINAL)) {
return handleCreateConnection();
}
if(isInState(STATE_INITIAL) && button == 1) {
updateTargetRequest();
updateTargetUnderMouse();
setConnectionSource(getTargetEditPart());
Command command = getCommand();
Request request = getTargetRequest();
//target elements of request have been set
if(command != null) {
setState(STATE_CONNECTION_STARTED);
setCurrentCommand(command);
viewer = getCurrentViewer();
// update request : initialize required extended data
Map extendedData = request.getExtendedData();
extendedData.put(SequenceRequestConstant.NEAREST_OCCURRENCE_SPECIFICATION_2, null);
extendedData.put(SequenceRequestConstant.OCCURRENCE_SPECIFICATION_LOCATION_2, null);
}
}
if(isInState(STATE_INITIAL) && button != 1) {
setState(STATE_INVALID);
handleInvalidInput();
}
if(isInState(STATE_CONNECTION_STARTED))
//Fake a drag to cause feedback to be displayed immediately on mouse down.
handleDrag();
return true;
}
/**
* @see org.eclipse.gef.tools.AbstractTool#handleMove()
*/
protected boolean handleMove() {
if(isInState(STATE_CONNECTION_STARTED) && viewer != getCurrentViewer())
return false;
if(isInState(STATE_CONNECTION_STARTED | STATE_INITIAL | STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) {
updateTargetRequest();
updateTargetUnderMouse();
showSourceFeedback();
showTargetFeedback();
setCurrentCommand(getCommand());
}
if(isInState(STATE_CONNECTION_STARTED)) {
// Expose the diagram as the user scrolls in the area handled by the
// autoexpose helper.
updateAutoexposeHelper();
}
return true;
}
/**
* Sets the source editpart for the creation
*
* @param source
* the source editpart node
*/
protected void setConnectionSource(EditPart source) {
if(connectionSource != null) {
connectionSource.removeEditPartListener(deactivationListener);
}
connectionSource = source;
if(connectionSource != null) {
connectionSource.addEditPartListener(deactivationListener);
}
}
/**
* Returns <code>true</code> if feedback is being shown.
*
* @return <code>true</code> if showing source feedback
*/
protected boolean isShowingSourceFeedback() {
return getFlag(FLAG_SOURCE_FEEDBACK);
}
/**
* Asks the source editpart to erase connection creation feedback.
*/
protected void eraseSourceFeedback() {
if(!isShowingSourceFeedback())
return;
setFlag(FLAG_SOURCE_FEEDBACK, false);
if(connectionSource != null) {
connectionSource.eraseSourceFeedback(getSourceRequest());
}
}
/**
* Scrolling can happen either in the {@link AbstractTool#STATE_INITIAL initial} state or
* once the source of the connection has been {@link AbstractConnectionCreationTool#STATE_CONNECTION_STARTED identified}.
*
* @see org.eclipse.gef.Tool#mouseWheelScrolled(org.eclipse.swt.widgets.Event, org.eclipse.gef.EditPartViewer)
*/
public void mouseWheelScrolled(Event event, EditPartViewer viewer) {
if(isInState(STATE_INITIAL | STATE_CONNECTION_STARTED))
performViewerMouseWheel(event, viewer);
}
/**
* Returns the request sent to the source node. The source node receives the same request
* that is used with the target node. The only difference is that at that time the
* request will be typed as {@link RequestConstants#REQ_CONNECTION_START}.
*
* @return the request used with the source node editpart
*/
protected Request getSourceRequest() {
return getTargetRequest();
}
/**
* Sends a show feedback request to the source editpart and sets the feedback flag.
*/
protected void showSourceFeedback() {
if(connectionSource != null) {
connectionSource.showSourceFeedback(getSourceRequest());
}
setFlag(FLAG_SOURCE_FEEDBACK, true);
}
/**
* Unloads or resets the tool if the state is in the terminal or invalid state.
*
* @see org.eclipse.gef.tools.AbstractTool#handleButtonUp(int)
*/
protected boolean handleButtonUp(int button) {
if(isInState(STATE_CONNECTION_STARTED)) {
handleCreateConnection();
}
setState(STATE_TERMINAL);
if(isInState(STATE_TERMINAL | STATE_INVALID)) {
handleFinished();
}
return true;
}
/**
* @see org.eclipse.gef.tools.AbstractTool#handleCommandStackChanged()
*/
protected boolean handleCommandStackChanged() {
if(!isInState(STATE_INITIAL)) {
if(getCurrentInput().isMouseButtonDown(1)) {
setState(STATE_INVALID);
} else {
setState(STATE_INITIAL);
}
handleInvalidInput();
return true;
}
return false;
}
/**
* @see org.eclipse.gef.Tool#deactivate()
*/
public void deactivate() {
eraseSourceFeedback();
setConnectionSource(null);
super.deactivate();
setState(STATE_TERMINAL);
viewer = null;
}
/**
* Calculate the cursor to display
*
* @see org.eclipse.gef.tools.AbstractConnectionCreationTool#calculateCursor()
*/
protected Cursor calculateCursor() {
if(isInState(STATE_CONNECTION_STARTED)) {
// Give some feedback so the user knows the area where autoscrolling
// will occur.
if(getAutoexposeHelper() != null) {
return SharedCursors.HAND;
} else {
// Give some feedback so the user knows that they can't drag
// outside the viewport.
if(getCurrentViewer() != null) {
Control control = getCurrentViewer().getControl();
if(control instanceof FigureCanvas) {
Viewport viewport = ((FigureCanvas)control).getViewport();
Rectangle rect = Rectangle.SINGLETON;
viewport.getClientArea(rect);
viewport.translateToParent(rect);
viewport.translateToAbsolute(rect);
if(!rect.contains(getLocation())) {
return getDisabledCursor();
}
}
}
}
}
Command command = getCurrentCommand();
if(command != null && command.canExecute()) {
EditPart ep = getTargetEditPart();
if(ep instanceof DiagramEditPart || ep instanceof CompartmentEditPart)
return CURSOR_TARGET_MENU;
}
return super.calculateCursor();
}
/**
* Get the command to execute
*
* @see org.eclipse.gmf.runtime.diagram.ui.tools.CreationTool#getCommand()
*
* @return the command
*/
@Override
protected Command getCommand() {
if(!antiScroll) {
EditPart targetPart = getTargetEditPart();
if(targetPart == null) {
return null;
}
Request req = getTargetRequest();
if(targetPart instanceof ConnectionNodeEditPart) {
// a message part is targeted. Instead, we must take the nearby lifeline part.
// redirect to the message part will be performed later in case we really want to draw a duration on a message
ConnectionNodeEditPart conn = (ConnectionNodeEditPart)targetPart;
EditPart source = ((ConnectionNodeEditPart)targetPart).getSource();
EditPart target = ((ConnectionNodeEditPart)targetPart).getTarget();
if(source instanceof NodeEditPart && target instanceof NodeEditPart) {
ConnectionAnchor sourceAnch = ((NodeEditPart)source).getSourceConnectionAnchor(conn);
ConnectionAnchor targetAnch = ((NodeEditPart)target).getSourceConnectionAnchor(conn);
double sourceDist = getLocation().getDistance(sourceAnch.getReferencePoint());
double targetDist = getLocation().getDistance(targetAnch.getReferencePoint());
if(sourceDist > targetDist) {
targetPart = target;
} else {
targetPart = source;
}
}
}
LifelineEditPart lifelinePart = SequenceUtil.getParentLifelinePart(targetPart);
if(lifelinePart instanceof LifelineEditPart) {
Object paramOcc1 = req.getExtendedData().get(SequenceRequestConstant.NEAREST_OCCURRENCE_SPECIFICATION);
List<OccurrenceSpecification> occ1List = SequenceUtil.getAsOccSpecList(paramOcc1);
Object paramOcc2 = req.getExtendedData().get(SequenceRequestConstant.NEAREST_OCCURRENCE_SPECIFICATION_2);
List<OccurrenceSpecification> occ2List = SequenceUtil.getAsOccSpecList(paramOcc2);
if(!occ1List.isEmpty() && !occ2List.isEmpty() && Collections.disjoint(occ1List, occ2List)) {
OccurrenceSpecification[] pair = SequenceUtil.getPairOfCorrespondingOccSpec(occ1List, occ2List);
if(pair != null && pair.length > 1) {
OccurrenceSpecification occ1 = pair[0];
OccurrenceSpecification occ2 = pair[1];
if(DurationConstraintHelper.endsOfSameMessage(occ1, occ2)) {
// call request on the link
EditPart part = SequenceUtil.getLinkedEditPart(lifelinePart, occ2);
if(part != null) {
return part.getCommand(req);
}
}
}
}
}
return targetPart.getCommand(req);
}
return null;
}
}