/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.gui.workflow.parts;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.FreeformLayer;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.CompoundSnapToHelper;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.SnapToGeometry;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.SnapToHelper;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.editpolicies.NonResizableEditPolicy;
import org.eclipse.gef.editpolicies.RootComponentEditPolicy;
import org.eclipse.gef.editpolicies.SnapFeedbackPolicy;
import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
import org.eclipse.gef.handles.MoveHandle;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import de.rcenvironment.core.component.model.api.ComponentShape;
import de.rcenvironment.core.component.model.spi.PropertiesChangeSupport;
import de.rcenvironment.core.component.workflow.model.api.Connection;
import de.rcenvironment.core.component.workflow.model.api.WorkflowDescription;
import de.rcenvironment.core.component.workflow.model.api.WorkflowLabel;
import de.rcenvironment.core.component.workflow.model.api.WorkflowNode;
import de.rcenvironment.core.gui.workflow.Activator;
import de.rcenvironment.core.gui.workflow.WorkflowNodeLabelConnectionHelper;
import de.rcenvironment.core.gui.workflow.editor.WorkflowEditor;
import de.rcenvironment.core.gui.workflow.editor.commands.WorkflowLabelMoveCommand;
import de.rcenvironment.core.gui.workflow.editor.commands.WorkflowNodeMoveCommand;
import de.rcenvironment.core.gui.workflow.editor.handlers.OvalBorderMoveHandle;
/**
* Part holding a {@link WorkflowDescription}.
*
* @author Heinrich Wendel
* @author Sascha Zur
* @author Oliver Seebach
* @author Jascha Riedel (sortWorkflowLabels)
*
*/
public class WorkflowPart extends AbstractGraphicalEditPart implements PropertyChangeListener, IAdaptable {
private final List<ConnectionWrapper> connections = new ArrayList<ConnectionWrapper>();
@Override
public final void activate() {
super.activate();
((PropertiesChangeSupport) getModel()).addPropertyChangeListener(this);
updateConnectionWrappers();
propertyChange(new PropertyChangeEvent(this, WorkflowDescription.PROPERTY_CONNECTIONS, null, null));
propertyChange(new PropertyChangeEvent(this, WorkflowDescription.PROPERTY_NODES, null, null));
propertyChange(new PropertyChangeEvent(this, WorkflowDescription.PROPERTY_LABEL, null, null));
}
@Override
public final void deactivate() {
super.deactivate();
((PropertiesChangeSupport) getModel()).removePropertyChangeListener(this);
}
@Override
public final void propertyChange(final PropertyChangeEvent evt) {
String prop = evt.getPropertyName();
if (WorkflowDescription.PROPERTY_CONNECTIONS.equals(prop)) {
updateConnectionWrappers();
for (Object part : getChildren()) {
if (part instanceof WorkflowNodePart) {
((WorkflowNodePart) part).refreshConnections();
// refresh labels
refreshConnectionLabels(part);
}
}
} else if (WorkflowDescription.PROPERTY_NODES.equals(prop)) {
((WorkflowDescription) getModel()).sortWorkflowNodesByZIndex();
refreshChildren();
} else if (WorkflowDescription.PROPERTY_LABEL.equals(prop)) {
((WorkflowDescription) getModel()).sortWorkflowLabelsByZIndex();
refreshChildren();
}
}
private void refreshConnectionLabels(Object part) {
boolean labelsShown = Activator.getInstance().getPreferenceStore().getBoolean(WorkflowEditor.SHOW_LABELS_PREFERENCE_KEY);
List<ConnectionPart> sourceAndTargetConnectionParts = new ArrayList<>();
sourceAndTargetConnectionParts.addAll(((WorkflowNodePart) part).getTargetConnections());
sourceAndTargetConnectionParts.addAll(((WorkflowNodePart) part).getSourceConnections());
for (ConnectionPart connectionPart : sourceAndTargetConnectionParts) {
if (labelsShown) {
connectionPart.showLabel();
} else {
connectionPart.hideLabel();
}
}
}
/**
* Getter class called by the children to get all connections represented by {@link ConnectionWrapper}s.
*
* @return List of {@link ConnectionWrapper}s.
*/
public final List<ConnectionWrapper> getConnections() {
return connections;
}
@Override
protected List<PropertiesChangeSupport> getModelChildren() {
List<PropertiesChangeSupport> combinedChildren = new ArrayList<PropertiesChangeSupport>();
combinedChildren.addAll(((WorkflowDescription) getModel()).getWorkflowLabels());
combinedChildren.addAll(((WorkflowDescription) getModel()).getWorkflowNodes());
return combinedChildren;
}
/**
* Helper method to update all {@link ConnectionWrapper}s when a {@link Connection} was added or removed.
*/
private void updateConnectionWrappers() {
connections.clear();
List<Connection> connectionsInModel = ((WorkflowDescription) getModel()).getConnections();
for (Connection c : connectionsInModel) {
final int minuseOne = -1;
int contains2 = minuseOne;
for (int i = 0; i < connections.size(); i++) {
if (connections.get(i).getSource().equals(c.getTargetNode())
&& connections.get(i).getTarget().equals(c.getSourceNode())) {
contains2 = i;
break;
}
}
int contains1 = minuseOne;
for (int i = 0; i < connections.size(); i++) {
if (connections.get(i).getSource().equals(c.getSourceNode())
&& connections.get(i).getTarget().equals(c.getTargetNode())) {
contains1 = i;
break;
}
}
if (contains2 != minuseOne) {
connections.get(contains2).setSourceArrow(true);
} else if (contains1 == minuseOne) {
ConnectionWrapper w = new ConnectionWrapper(c.getSourceNode(), c.getTargetNode());
w.setTargetArrow(true);
connections.add(w);
}
}
// Add up channels in both directions to show number of channels on GUI
for (ConnectionWrapper wrapper : connections) {
for (Connection c : connectionsInModel) {
if ((c.getSourceNode().getIdentifier().equals(wrapper.getSource().getIdentifier())
&& c.getTargetNode().getIdentifier().equals(wrapper.getTarget().getIdentifier()))
|| c.getTargetNode().getIdentifier().equals(wrapper.getSource().getIdentifier())
&& c.getSourceNode().getIdentifier().equals(wrapper.getTarget().getIdentifier())) {
wrapper.incrementNumberOfConnections();
}
}
}
}
@Override
protected final IFigure createFigure() {
Figure f = new BackgroundLayer();
f.setBorder(new MarginBorder(3));
f.setLayoutManager(new FreeformLayout());
// Create the static router for the connection layer
ConnectionLayer connLayer = (ConnectionLayer) getLayer(LayerConstants.CONNECTION_LAYER);
connLayer.setAntialias(SWT.ON);
connLayer.setConnectionRouter(new CustomShortestPathConnectionRouter(f));
return f;
}
@Override
protected void createEditPolicies() {
installEditPolicy(EditPolicy.COMPONENT_ROLE, new RootComponentEditPolicy());
installEditPolicy(EditPolicy.LAYOUT_ROLE, new WorkflowXYLayoutEditPolicy());
installEditPolicy("Snap Feedback", new SnapFeedbackPolicy());
}
/**
* Policy responsible for creating new WorkflowNodes.
*
* @author Heinrich Wendel
* @author Sascha Zur
*/
class WorkflowXYLayoutEditPolicy extends XYLayoutEditPolicy {
@Override
protected Command createChangeConstraintCommand(final ChangeBoundsRequest request, final EditPart child, final Object constraint) {
if (child instanceof WorkflowNodePart && constraint instanceof Rectangle) {
// Find all connections between the selected nodes
List<Connection> relatedConnections = new ArrayList<>();
if (getModel() instanceof WorkflowDescription) {
WorkflowDescription workflowDescription = (WorkflowDescription) getModel();
for (Connection connection : workflowDescription.getConnections()) {
if (((connection.getTargetNode().getIdentifier().equals(((WorkflowNode) child.getModel()).getIdentifier()))
|| (connection.getSourceNode().getIdentifier().equals(((WorkflowNode) child.getModel()).getIdentifier())))
&& checkIfSourceAndTargetAreSelected(connection.getSourceNode(), connection.getTargetNode(), child.getViewer()
.getSelectedEditParts())) {
relatedConnections.add(connection);
}
}
}
return new WorkflowNodeMoveCommand((WorkflowNode) child.getModel(), request, (Rectangle) constraint, relatedConnections);
}
if (child instanceof WorkflowLabelPart && constraint instanceof Rectangle) {
return new WorkflowLabelMoveCommand((WorkflowLabel) child.getModel(), request, (Rectangle) constraint);
}
return super.createChangeConstraintCommand(request, child, constraint);
}
@Override
protected Command createChangeConstraintCommand(final EditPart child, final Object constraint) {
return null;
}
@Override
protected EditPolicy createChildEditPolicy(EditPart child) {
if (child instanceof WorkflowNodePart) {
return new NoBorderEditPolicy(child);
} else {
return super.createChildEditPolicy(child);
}
}
private boolean checkIfSourceAndTargetAreSelected(WorkflowNode sourceNode, WorkflowNode targetNode, List<?> selectedEditParts) {
boolean sourceContained = false;
boolean targetContained = false;
for (Object part : selectedEditParts) {
if (part instanceof WorkflowNodePart) {
WorkflowNode selectedNode = (WorkflowNode) ((WorkflowNodePart) part).getModel();
if (sourceNode.getIdentifier().equals(selectedNode.getIdentifier())) {
sourceContained = true;
}
if (targetNode.getIdentifier().equals(selectedNode.getIdentifier())) {
targetContained = true;
}
}
}
return sourceContained && targetContained;
}
/**
* New policy to remove the handles from {@link WorkflowNodePart}s.
*
* @author Sascha Zur
*/
private class NoBorderEditPolicy extends NonResizableEditPolicy {
private final EditPart child;
NoBorderEditPolicy(EditPart child) {
this.child = child;
}
@Override
protected List<MoveHandle> createSelectionHandles() {
List<MoveHandle> list = new ArrayList<>();
if (isDragAllowed()) {
if (child instanceof WorkflowNodePart
&& ((WorkflowNode) ((WorkflowNodePart) child).getModel()).getComponentDescription().getComponentInstallation()
.getComponentRevision().getComponentInterface().getShape() == ComponentShape.CIRCLE) {
list.add(new OvalBorderMoveHandle((GraphicalEditPart) child));
} else {
list.add(new MoveHandle((GraphicalEditPart) child));
}
}
return list;
}
}
@Override
protected Command getCreateCommand(final CreateRequest request) {
Object childClass = request.getNewObjectType();
if (childClass == WorkflowNode.class) {
WorkflowNodeLabelConnectionHelper helper = new WorkflowNodeLabelConnectionHelper((WorkflowNode) request.getNewObject(),
(WorkflowDescription) getHost().getModel(), (Rectangle) getConstraintFor(request));
return helper.createCommand();
}
if (childClass == WorkflowLabel.class) {
WorkflowNodeLabelConnectionHelper helper = new WorkflowNodeLabelConnectionHelper((WorkflowLabel) request.getNewObject(),
(WorkflowDescription) getHost().getModel(), (Rectangle) getConstraintFor(request));
return helper.createCommand();
}
return null;
}
}
@Override
public Object getAdapter(@SuppressWarnings("rawtypes") Class key) {
// Enable Snap to grid/geometry in wf editor for all parts.
if (key == SnapToHelper.class) {
List<SnapToHelper> helpers = new ArrayList<SnapToHelper>();
if (Boolean.TRUE.equals(getViewer().getProperty(SnapToGeometry.PROPERTY_SNAP_ENABLED))) {
helpers.add(new SnapToGeometry(this));
}
if (Boolean.TRUE.equals(getViewer().getProperty(SnapToGrid.PROPERTY_GRID_ENABLED))) {
helpers.add(new SnapToGrid(this));
}
if (helpers.size() == 0) {
return null;
} else {
return new CompoundSnapToHelper(helpers.toArray(new SnapToHelper[0]));
}
}
return super.getAdapter(key);
}
/**
* Class for a custom background image.
*
* @author Heinrich Wendel
*/
private class BackgroundLayer extends FreeformLayer implements FigureListener {
private final Image orgImage;
private Image image;
private int x;
private int y;
BackgroundLayer() {
orgImage = Activator.getInstance().getImageRegistry().get(Activator.IMAGE_WORKFLOW_EDITOR_BACKGROUND);
resize();
addFigureListener(this);
}
private void resize() {
Rectangle targetRect = getBounds().getCopy();
if (orgImage != null && targetRect.height != 0) {
float scaleX = (float) targetRect.width / (float) orgImage.getBounds().width;
float scaleY = (float) targetRect.height / (float) orgImage.getBounds().height;
int sizeX;
int sizeY;
if (scaleX < scaleY) {
sizeX = (int) (orgImage.getBounds().width * scaleX);
sizeY = (int) (orgImage.getBounds().height * scaleX);
} else {
sizeX = (int) (orgImage.getBounds().width * scaleY);
sizeY = (int) (orgImage.getBounds().height * scaleY);
}
x = (targetRect.width - sizeX) / 2;
y = (targetRect.height - sizeY) / 2;
if (image != null) {
image.dispose();
}
image = new Image(Display.getDefault(), orgImage.getImageData().scaledTo(sizeX, sizeY));
}
}
@Override
protected void paintFigure(final Graphics graphics) {
if (image != null) {
graphics.drawImage(image, x, y);
}
super.paintFigure(graphics);
}
@Override
public void figureMoved(final IFigure arg0) {
resize();
}
}
}