blob: 4027ea6e2d745693f610a82845f9e9db64bce992 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2020 Kiel University and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Kiel University - initial API and implementation
* Camille Letavernier (CEA LIST) - Bug 485905
*******************************************************************************/
package org.eclipse.sirius.diagram.elk;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionLocator;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.Shape;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.elk.alg.layered.options.CrossingMinimizationStrategy;
import org.eclipse.elk.alg.layered.options.LayeredOptions;
import org.eclipse.elk.core.IGraphLayoutEngine;
import org.eclipse.elk.core.data.LayoutMetaDataService;
import org.eclipse.elk.core.data.LayoutOptionData;
import org.eclipse.elk.core.math.ElkPadding;
import org.eclipse.elk.core.math.KVector;
import org.eclipse.elk.core.options.CoreOptions;
import org.eclipse.elk.core.options.EdgeLabelPlacement;
import org.eclipse.elk.core.options.NodeLabelPlacement;
import org.eclipse.elk.core.options.SizeConstraint;
import org.eclipse.elk.core.service.IDiagramLayoutConnector;
import org.eclipse.elk.core.service.LayoutMapping;
import org.eclipse.elk.core.util.BasicProgressMonitor;
import org.eclipse.elk.core.util.ElkUtil;
import org.eclipse.elk.graph.ElkConnectableShape;
import org.eclipse.elk.graph.ElkEdge;
import org.eclipse.elk.graph.ElkEdgeSection;
import org.eclipse.elk.graph.ElkGraphElement;
import org.eclipse.elk.graph.ElkLabel;
import org.eclipse.elk.graph.ElkNode;
import org.eclipse.elk.graph.ElkPort;
import org.eclipse.elk.graph.properties.IProperty;
import org.eclipse.elk.graph.properties.IPropertyHolder;
import org.eclipse.elk.graph.properties.Property;
import org.eclipse.elk.graph.util.ElkGraphUtil;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.transaction.impl.InternalTransactionalEditingDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gmf.runtime.diagram.ui.editparts.AbstractBorderItemEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.CompartmentEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ConnectionEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ResizableCompartmentEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeNodeEditPart;
import org.eclipse.gmf.runtime.diagram.ui.figures.ResizableCompartmentFigure;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditor;
import org.eclipse.gmf.runtime.draw2d.ui.figures.WrappingLabel;
import org.eclipse.gmf.runtime.gef.ui.figures.NodeFigure;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.sirius.common.tools.api.util.StringUtil;
import org.eclipse.sirius.diagram.DDiagramElement;
import org.eclipse.sirius.diagram.DDiagramElementContainer;
import org.eclipse.sirius.diagram.DNode;
import org.eclipse.sirius.diagram.DNodeContainer;
import org.eclipse.sirius.diagram.DNodeList;
import org.eclipse.sirius.diagram.LabelPosition;
import org.eclipse.sirius.diagram.NodeStyle;
import org.eclipse.sirius.diagram.business.api.query.DDiagramElementQuery;
import org.eclipse.sirius.diagram.business.internal.query.DDiagramElementContainerExperimentalQuery;
import org.eclipse.sirius.diagram.business.internal.query.DNodeContainerExperimentalQuery;
import org.eclipse.sirius.diagram.description.BooleanLayoutOption;
import org.eclipse.sirius.diagram.description.CustomLayoutConfiguration;
import org.eclipse.sirius.diagram.description.DescriptionPackage;
import org.eclipse.sirius.diagram.description.DoubleLayoutOption;
import org.eclipse.sirius.diagram.description.EnumLayoutOption;
import org.eclipse.sirius.diagram.description.EnumLayoutValue;
import org.eclipse.sirius.diagram.description.EnumSetLayoutOption;
import org.eclipse.sirius.diagram.description.IntegerLayoutOption;
import org.eclipse.sirius.diagram.description.LayoutOption;
import org.eclipse.sirius.diagram.description.LayoutOptionTarget;
import org.eclipse.sirius.diagram.description.StringLayoutOption;
import org.eclipse.sirius.diagram.ui.business.api.query.EditPartQuery;
import org.eclipse.sirius.diagram.ui.edit.api.part.AbstractDiagramElementContainerEditPart;
import org.eclipse.sirius.diagram.ui.edit.api.part.IAbstractDiagramNodeEditPart;
import org.eclipse.sirius.diagram.ui.edit.api.part.IDDiagramEditPart;
import org.eclipse.sirius.diagram.ui.edit.api.part.IDiagramElementEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.AbstractDNodeContainerCompartmentEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.AbstractDNodeListCompartmentEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListElementEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.SiriusDescriptionCompartmentEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.SiriusNoteEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.SiriusTextEditPart;
import org.eclipse.sirius.diagram.ui.internal.refresh.GMFHelper;
import org.eclipse.sirius.diagram.ui.tools.api.graphical.edit.styles.IBorderItemOffsets;
import org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.AlphaDropShadowBorder;
import org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.SiriusWrapLabel;
import org.eclipse.sirius.viewpoint.LabelAlignment;
import org.eclipse.sirius.viewpoint.LabelStyle;
import org.eclipse.sirius.viewpoint.Style;
import org.eclipse.swt.SWTException;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import com.google.common.collect.BiMap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* Diagram layout connector that is able to generically layout diagrams generated by Sirius. The internal KGraph graph
* structure is built from the structure of edit parts in the diagram. The new layout is applied to the diagram using
* {@link GmfLayoutEditPolicy}, which creates a {@link GmfLayoutCommand} to directly manipulate data in the GMF notation
* model, where layout information is stored persistently.
*
* Copied from org.eclipse.elk.conn.gmf.GmfDiagramLayoutConnector of commit a54c87f94fc4a9bb9ff426311c49ada9d3e72b97.
*
* This component has been added the capacity to launch diagram layout computing.
*
* It also has been added the general options setup from VSM specification.
*
* @kieler.design proposed by msp
* @kieler.rating yellow 2012-07-19 review KI-20 by cds, jjc
* @author ars
* @author msp
* @author <a href="mailto:pierre.guilet@obeo.fr">Pierre Guilet</a>
*
*/
@Singleton
public class ElkDiagramLayoutConnector implements IDiagramLayoutConnector {
/**
* A layout configuration that the provider should use to configure its algorithm.
*/
protected CustomLayoutConfiguration layoutConfiguration;
/** list of connection edit parts that were found in the diagram. */
public static final IProperty<List<ConnectionEditPart>> CONNECTIONS = new Property<List<ConnectionEditPart>>("gmf.connections");
/** diagram edit part of the currently layouted diagram. */
public static final IProperty<DiagramEditPart> DIAGRAM_EDIT_PART = new Property<DiagramEditPart>("gmf.diagramEditPart");
/** the command that applies the transferred layout to the diagram. */
public static final IProperty<Command> LAYOUT_COMMAND = new Property<Command>("gmf.applyLayoutCommand");
/** the offset to add for all coordinates. */
public static final IProperty<KVector> COORDINATE_OFFSET = new Property<KVector>("gmf.coordinateOffset");
public static final String PREF_EXEC_TIME_MEASUREMENT = "elk.exectime.measure";
@Inject
private IEditPartFilter editPartFilter;
@Inject
private IGraphLayoutEngine graphLayoutEngine;
/**
* Export the given layout graph in a file. The file will be saved in the directory specified in java.io.tmpdir vm
* argument.
*
* @param graphToStore
* the layout graph to store.
* @param diagramName
* the name if the diagram used as file name.
* @param prefix
* a prefix that can be used in the file name.
* @param openDialog
* whether we should indicate by a dialog that the diagram as been saved as file.
*
*/
public static void storeResult(final ElkNode graphToStore, final String diagramName, String suffix, boolean openDialog) {
String fileName;
if (StringUtil.isEmpty(suffix)) {
fileName = diagramName + ".elkg";
} else {
fileName = diagramName + "_" + suffix + ".elkg";
}
URI exportUri = URI.createFileURI(System.getProperty("java.io.tmpdir") + fileName);
ResourceSet resourceSet = new ResourceSetImpl();
Resource resource = resourceSet.createResource(exportUri);
// Disable the layout stored in this graph to avoid an automatic layout during the opening in "Layout Graph"
// view
graphToStore.setProperty(CoreOptions.NO_LAYOUT, true);
resource.getContents().add(graphToStore);
try {
resource.save(Collections.emptyMap());
if (openDialog) {
MessageDialog.openInformation(PlatformUI.getWorkbench().getDisplay().getActiveShell(), "Export diagram as ELK Graph",
MessageFormat.format("Current diagram has been successfully exported into {0}", URI.decode(exportUri.toString())));
}
} catch (IOException e) {
System.out.println(e);
// ignore the exception
}
graphToStore.setProperty(CoreOptions.NO_LAYOUT, false);
}
/**
* Calculates the absolute bounds of the given figure.
*
* @param figure
* a figure
* @return the absolute bounds
*/
public static Rectangle getAbsoluteBounds(final IFigure figure) {
Rectangle bounds = new Rectangle(figure.getBounds()) {
static final long serialVersionUID = 1;
@Override
public void performScale(final double factor) {
// don't perform any scaling to avoid distortion by the zoom
// level
}
};
figure.translateToAbsolute(bounds);
return bounds;
}
/**
* Sets the layout configuration that the provider should use to configure its algorithm.
*
* @param layoutConfiguration
* the layout configuration that the provider should use to configure its algorithm.
*/
public void setLayoutConfiguration(CustomLayoutConfiguration layoutConfiguration) {
this.layoutConfiguration = layoutConfiguration;
}
/**
* If the editPart is a container and is not a workspace image or a regions, the default shadow border size is
* returned. Otherwise, 0 is returned. See
* {@link AbstractDiagramElementContainerEditPart#addDropShadow(NodeFigure,IFigure)}.
*
* @param editPart
* an edit part
* @return The shadow border size of the edit part
*/
public static double getShadowBorderSize(final EditPart editPart) {
double shadowBorderSize = 0;
if (editPart instanceof AbstractDiagramElementContainerEditPart && ((AbstractDiagramElementContainerEditPart) editPart).isShadowBorderNeeded()) {
shadowBorderSize = AlphaDropShadowBorder.getDefaultShadowSize();
}
return shadowBorderSize;
}
/**
* If the workbench part is a diagram editor, returns that. Otherwise, returns {@code null}. This is more or less
* just a fancy cast.
*
* @param workbenchPart
* the workbench part to check.
* @return the workbench part as a diagram editor, or {@code null}.
*/
protected DiagramEditor getDiagramEditor(final IWorkbenchPart workbenchPart) {
if (workbenchPart instanceof DiagramEditor) {
return (DiagramEditor) workbenchPart;
}
return null;
}
/**
* Build a KGraph instance for the given diagram. The resulting layout graph should reflect the structure of the
* original diagram. All graph elements must have {@link org.eclipse.elk.core.klayoutdata.KShapeLayout
* KShapeLayouts} or {@link org.eclipse.elk.core.klayoutdata.KEdgeLayout KEdgeLayouts} attached, and their
* modification flags must be set to {@code false}.
*
* <p>
* At least one of the two parameters must be non-null.
* </p>
*
* <p>
* This method is usually called from the UI thread.
* </p>
*
* @param diagramEditPart
* the diagram edit part for which layout is performed
* @param layoutedPart
* the part(s) for which layout is performed, or {@code null} if the whole diagram shall be layouted
* @return a layout graph mapping, or {@code null} if the given workbench part or diagram part is not supported
*/
public LayoutMapping buildLayoutGraph(final DiagramEditPart diagramEditPart, final Object layoutedPart) {
// choose the layout root edit part
IGraphicalEditPart layoutRootPart = null;
List<ShapeNodeEditPart> selectedParts = null;
if (layoutedPart instanceof ShapeNodeEditPart || layoutedPart instanceof DiagramEditPart) {
layoutRootPart = (IGraphicalEditPart) layoutedPart;
} else if (layoutedPart instanceof IGraphicalEditPart) {
EditPart tgEditPart = ((IGraphicalEditPart) layoutedPart).getTopGraphicEditPart();
if (tgEditPart instanceof ShapeNodeEditPart) {
layoutRootPart = (IGraphicalEditPart) tgEditPart;
}
} else if (layoutedPart instanceof Collection) {
Collection<?> selection = (Collection<?>) layoutedPart;
// determine the layout root part from the selection
for (Object object : selection) {
if (object instanceof IGraphicalEditPart) {
if (layoutRootPart != null) {
EditPart parent = commonParent(layoutRootPart, (EditPart) object);
if (parent != null && !(parent instanceof RootEditPart)) {
layoutRootPart = (IGraphicalEditPart) parent;
}
} else if (!(object instanceof ConnectionEditPart)) {
layoutRootPart = (IGraphicalEditPart) object;
}
}
}
// build a list of edit parts that shall be layouted completely
if (layoutRootPart != null) {
selectedParts = new ArrayList<ShapeNodeEditPart>(selection.size());
for (Object object : selection) {
if (object instanceof IGraphicalEditPart) {
EditPart editPart = (EditPart) object;
while (editPart != null && editPart.getParent() != layoutRootPart) {
editPart = editPart.getParent();
}
if (editPart instanceof ShapeNodeEditPart && editPartFilter.filter(editPart) && !selectedParts.contains(editPart)) {
if (editPart instanceof SiriusNoteEditPart) {
if (new EditPartQuery((SiriusNoteEditPart) editPart).isMovableByAutomaticLayout(Collections.EMPTY_LIST)) {
selectedParts.add((ShapeNodeEditPart) editPart);
}
} else {
selectedParts.add((ShapeNodeEditPart) editPart);
}
}
}
}
}
}
// create the mapping
LayoutMapping mapping = buildLayoutGraph(selectedParts, diagramEditPart);
return mapping;
}
/**
* Determine the lowest common parent of the two edit parts.
*
* @param editPart1
* the first edit part
* @param editPart2
* the second edit part
* @return the common parent, or {@code null} if there is none
*/
protected static EditPart commonParent(final EditPart editPart1, final EditPart editPart2) {
EditPart ep1 = editPart1;
EditPart ep2 = editPart2;
do {
if (isParent(ep1, ep2)) {
return ep1;
}
if (isParent(ep2, ep1)) {
return ep2;
}
ep1 = ep1.getParent();
ep2 = ep2.getParent();
} while (ep1 != null && ep2 != null);
return null;
}
/**
* Determine whether the first edit part is a parent of or equals the second one.
*
* @param parent
* the tentative parent
* @param child
* the tentative child
* @return true if the parent is actually a parent of the child
*/
protected static boolean isParent(final EditPart parent, final EditPart child) {
EditPart editPart = child;
do {
if (editPart == parent) {
return true;
}
editPart = editPart.getParent();
} while (editPart != null);
return false;
}
public <E extends Enum<E>> EnumSet<E> of(E e, EnumSet<E> enumSet) {
enumSet.add(e);
return enumSet;
}
/**
* Creates the actual mapping given an edit part which functions as the root for the layout.
*
* @param layoutRootPart
* the layout root edit part
* @param selection
* a selection of contained edit parts to process, or {@code null} if the whole content shall be
* processed
* @param diagramEditPart
* the diagram edit part, or {@code null}
* @return a layout graph mapping
*/
protected LayoutMapping buildLayoutGraph(final List<ShapeNodeEditPart> selection, final DiagramEditPart diagramEditPart) {
LayoutMapping mapping = new LayoutMapping(null);
mapping.setProperty(CONNECTIONS, new LinkedList<ConnectionEditPart>());
mapping.setParentElement(diagramEditPart);
// find the diagram edit part
mapping.setProperty(DIAGRAM_EDIT_PART, diagramEditPart);
Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOevrrideMap = constructElkOptionTargetToOptionsMap();
// start with the whole diagram as root for layout. We cannot start from a diagram element even with a selection
// layouting because if it has ports it is not supported. The top node is there only to support some meta data.
ElkNode topNode = ElkGraphUtil.createGraph();
applyOptionsRelatedToElementTarget(topNode, elkTargetToOptionsOevrrideMap);
Rectangle rootBounds = diagramEditPart.getFigure().getBounds();
String labelText = diagramEditPart.getDiagramView().getName();
if (labelText.length() > 0) {
ElkLabel label = ElkGraphUtil.createLabel(topNode);
label.setText(labelText);
}
topNode.setDimensions(rootBounds.width, rootBounds.height);
mapping.getGraphMap().put(topNode, diagramEditPart);
// we set the ELK algorithm to use from viewpoint id defined.
topNode.setProperty(CoreOptions.ALGORITHM, layoutConfiguration.getId().trim());
mapping.setLayoutGraph(topNode);
if (selection != null && !selection.isEmpty()) {
// layout only the selected elements
double minx = Integer.MAX_VALUE;
double miny = Integer.MAX_VALUE;
for (ShapeNodeEditPart editPart : selection) {
ElkNode node = createNode(mapping, editPart, topNode, elkTargetToOptionsOevrrideMap);
minx = Math.min(minx, node.getX());
miny = Math.min(miny, node.getY());
buildLayoutGraphRecursively(mapping, node, editPart, elkTargetToOptionsOevrrideMap);
}
mapping.setProperty(COORDINATE_OFFSET, new KVector(minx, miny));
} else {
// traverse all children of the layout root part
buildLayoutGraphRecursively(mapping, topNode, diagramEditPart, elkTargetToOptionsOevrrideMap);
}
// transform all connections in the selected area
processConnections(mapping, elkTargetToOptionsOevrrideMap);
return mapping;
}
/**
* Construct a map associated option targets to all corresponding options that are overridden in the VSM.
*
* @return a map of option targets to corresponding options.
*/
private Map<LayoutOptionTarget, Set<LayoutOption>> constructElkOptionTargetToOptionsMap() {
Map<LayoutOptionTarget, Set<LayoutOption>> resultMap = new HashMap<>();
resultMap.put(LayoutOptionTarget.EDGE, new HashSet<>());
resultMap.put(LayoutOptionTarget.LABEL, new HashSet<>());
resultMap.put(LayoutOptionTarget.NODE, new HashSet<>());
resultMap.put(LayoutOptionTarget.PARENT, new HashSet<>());
resultMap.put(LayoutOptionTarget.PORTS, new HashSet<>());
if (layoutConfiguration != null) {
EList<LayoutOption> layoutOptions = layoutConfiguration.getLayoutOptions();
for (LayoutOption layoutOption : layoutOptions) {
EList<LayoutOptionTarget> targets = layoutOption.getTargets();
for (LayoutOptionTarget layoutOptionTarget : targets) {
Set<LayoutOption> optionsSet = resultMap.get(layoutOptionTarget);
optionsSet.add(layoutOption);
}
}
}
return resultMap;
}
private void applyOptionsRelatedToElementTarget(ElkGraphElement elkElement, Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
if (layoutConfiguration != null) {
// we set the global options.
if (elkElement instanceof ElkNode) {
Set<LayoutOption> layoutOptionsSet = elkTargetToOptionsOverrideMap.get(LayoutOptionTarget.NODE);
applyOptions(elkElement, layoutOptionsSet);
} else if (elkElement instanceof ElkLabel) {
Set<LayoutOption> layoutOptionsSet = elkTargetToOptionsOverrideMap.get(LayoutOptionTarget.LABEL);
applyOptions(elkElement, layoutOptionsSet);
} else if (elkElement instanceof ElkPort) {
Set<LayoutOption> layoutOptionsSet = elkTargetToOptionsOverrideMap.get(LayoutOptionTarget.PORTS);
applyOptions(elkElement, layoutOptionsSet);
} else if (elkElement instanceof ElkEdge) {
Set<LayoutOption> layoutOptionsSet = elkTargetToOptionsOverrideMap.get(LayoutOptionTarget.EDGE);
applyOptions(elkElement, layoutOptionsSet);
}
}
}
private void applyParentNodeOption(ElkGraphElement elkElement, Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
Set<LayoutOption> layoutOptionsSet = elkTargetToOptionsOverrideMap.get(LayoutOptionTarget.PARENT);
applyOptions(elkElement, layoutOptionsSet);
}
private void applyOptions(ElkGraphElement elkElement, Set<LayoutOption> layoutOptionsSet) {
for (LayoutOption layoutOption : layoutOptionsSet) {
LayoutOptionData layoutProperty = LayoutMetaDataService.getInstance().getOptionData(layoutOption.getId());
switch (layoutOption.eClass().getClassifierID()) {
case DescriptionPackage.ENUM_LAYOUT_OPTION:
EnumLayoutOption enumOption = (EnumLayoutOption) layoutOption;
int enumValueCount = layoutProperty.getEnumValueCount();
Enum<?> elkEnum = null;
int i = 0;
while (i < enumValueCount && elkEnum == null) {
layoutProperty.getEnumValue(i);
Enum<?> enumValue = layoutProperty.getEnumValue(i);
if (enumOption.getValue().getName().equals(enumValue.name())) {
elkEnum = enumValue;
}
i++;
}
elkElement.setProperty(layoutProperty, elkEnum);
break;
case DescriptionPackage.ENUM_SET_LAYOUT_OPTION:
EnumSetLayoutOption enumSetOption = (EnumSetLayoutOption) layoutOption;
enumValueCount = layoutProperty.getEnumValueCount();
if (enumValueCount > 0) {
EnumSet enumSet = EnumSet.noneOf(layoutProperty.getEnumValue(0).getDeclaringClass());
List<EnumLayoutValue> values = enumSetOption.getValues();
for (EnumLayoutValue enumLayoutValue : values) {
i = 0;
while (i < enumValueCount) {
Enum enumValue = layoutProperty.getEnumValue(i);
if (enumLayoutValue.getName().equals(enumValue.name())) {
enumSet = of(enumValue, enumSet);
break;
}
i++;
}
}
elkElement.setProperty(layoutProperty, enumSet);
}
break;
case DescriptionPackage.BOOLEAN_LAYOUT_OPTION:
BooleanLayoutOption booleanOption = (BooleanLayoutOption) layoutOption;
elkElement.setProperty(layoutProperty, booleanOption.isValue());
break;
case DescriptionPackage.INTEGER_LAYOUT_OPTION:
IntegerLayoutOption integerOption = (IntegerLayoutOption) layoutOption;
elkElement.setProperty(layoutProperty, integerOption.getValue());
break;
case DescriptionPackage.DOUBLE_LAYOUT_OPTION:
DoubleLayoutOption doubleOption = (DoubleLayoutOption) layoutOption;
elkElement.setProperty(layoutProperty, doubleOption.getValue());
break;
case DescriptionPackage.STRING_LAYOUT_OPTION:
StringLayoutOption stringOption = (StringLayoutOption) layoutOption;
elkElement.setProperty(layoutProperty, stringOption.getValue().trim());
break;
default:
break;
}
}
}
/**
* Transfer all layout data from the last created KGraph instance to the original diagram. The diagram is not
* modified yet, but all required preparations are performed. This is separated from
* {@link #applyLayout(LayoutMapping)} to allow better code modularization.
*
* @param mapping
* a layout mapping that was created by this layout connector
*/
public void transferLayout(final LayoutMapping mapping) {
// create a new request to change the layout
ApplyLayoutRequest applyLayoutRequest = new ApplyLayoutRequest();
for (Entry<ElkGraphElement, Object> entry : mapping.getGraphMap().entrySet()) {
if (!(entry.getValue() instanceof DiagramEditPart)) {
ElkGraphElement graphElement = entry.getKey();
IGraphicalEditPart part = (IGraphicalEditPart) entry.getValue();
if (!(part instanceof AbstractDNodeListCompartmentEditPart || part instanceof AbstractDNodeContainerCompartmentEditPart)) {
// We ignore compartment edit part that are created into ELK side just to have good layout results
applyLayoutRequest.addElement(graphElement, part);
}
}
}
ElkNode layoutGraph = mapping.getLayoutGraph();
applyLayoutRequest.setUpperBound(layoutGraph.getWidth(), layoutGraph.getHeight());
// correct the layout by adding the offset determined from the selection
KVector offset = mapping.getProperty(COORDINATE_OFFSET);
if (offset != null) {
addOffset(mapping.getLayoutGraph(), offset);
}
// check the validity of the editing domain to catch cases where it is
// disposed
DiagramEditPart diagramEditPart = mapping.getProperty(DIAGRAM_EDIT_PART);
if (((InternalTransactionalEditingDomain) diagramEditPart.getEditingDomain()).getChangeRecorder() != null) {
// retrieve a command for the request; the command is created by
// GmfLayoutEditPolicy
Command applyLayoutCommand = diagramEditPart.getCommand(applyLayoutRequest);
mapping.setProperty(LAYOUT_COMMAND, applyLayoutCommand);
}
}
/**
* Add the given offset to all direct children of the given graph.
*
* @param parentNode
* the parent node
* @param offset
* the offset to add
*/
protected static void addOffset(final ElkNode parentNode, final KVector offset) {
// correct the offset with the minimal computed coordinates
double minx = Integer.MAX_VALUE;
double miny = Integer.MAX_VALUE;
for (ElkNode child : parentNode.getChildren()) {
minx = Math.min(minx, child.getX());
miny = Math.min(miny, child.getY());
}
// add the corrected offset
offset.add(-minx, -miny);
ElkUtil.translate(parentNode, offset.x, offset.y);
}
/**
* Apply the transferred layout to the original diagram. This final step is where the actual change to the diagram
* is done. This method is always called after {@link #transferLayout(LayoutMapping)} has been done.
*
* @param mapping
* a layout mapping that was created by this layout connector
*/
public Command getApplyCommand(final LayoutMapping mapping) {
Command applyLayoutCommand = mapping.getProperty(LAYOUT_COMMAND);
return applyLayoutCommand;
}
/**
* Recursively builds a layout graph by analyzing the children of the given edit part.
*
* @param mapping
* the layout mapping
* @param parentLayoutNode
* the {@link ElkNode} corresponding to the parent edit part of the current edit part
* @param currentEditPart
* the currently analyzed edit part
* @param elkTargetToOptionsOverrideMap
* a map of option targets to corresponding options.
*/
protected void buildLayoutGraphRecursively(final LayoutMapping mapping, final ElkNode parentLayoutNode, final IGraphicalEditPart currentEditPart,
Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
// iterate through the children of the element
double maxChildShadowBorderSize = -1;
for (Object obj : currentEditPart.getChildren()) {
// check visibility of the child
if (obj instanceof IGraphicalEditPart) {
IFigure figure = ((IGraphicalEditPart) obj).getFigure();
if (!figure.isVisible()) {
continue;
}
}
// process a port (border item)
if (obj instanceof AbstractBorderItemEditPart) {
AbstractBorderItemEditPart borderItem = (AbstractBorderItemEditPart) obj;
if (editPartFilter.filter(borderItem)) {
createPort(mapping, borderItem, currentEditPart, parentLayoutNode, elkTargetToOptionsOverrideMap);
}
// process a compartment, which may contain other elements
} else if (obj instanceof ResizableCompartmentEditPart && ((CompartmentEditPart) obj).getChildren().size() > 0) {
CompartmentEditPart compartment = (CompartmentEditPart) obj;
if (editPartFilter.filter(compartment)) {
boolean compExp = true;
IFigure compartmentFigure = compartment.getFigure();
if (compartmentFigure instanceof ResizableCompartmentFigure) {
ResizableCompartmentFigure resizCompFigure = (ResizableCompartmentFigure) compartmentFigure;
// check whether the compartment is collapsed
compExp = resizCompFigure.isExpanded();
}
if (compExp) {
ElkNode intermediateNode = parentLayoutNode;
if (currentEditPart instanceof IDiagramElementEditPart) {
IDiagramElementEditPart ideep = (IDiagramElementEditPart) currentEditPart;
DDiagramElement dde = ideep.resolveDiagramElement();
if (dde instanceof DNodeList || (dde instanceof DNodeContainer && (new DNodeContainerExperimentalQuery((DNodeContainer) dde)).isHorizontaltackContainer()
|| new DNodeContainerExperimentalQuery((DNodeContainer) dde).isVerticalStackContainer())) {
// Create a node representing the compartment
intermediateNode = createNode(mapping, compartment, parentLayoutNode, elkTargetToOptionsOverrideMap);
// Add some additional layout option to its container to have "fit" layout result
Dimension topLeftInsets = GMFHelper.getContainerTopLeftInsetsAfterLabel((Node) compartment.getNotationView(), true);
Dimension borderSize = GMFHelper.getBorderSize((DDiagramElementContainer) dde);
int separatorLineHeight = 1;
ElkPadding padding;
if (dde instanceof DNodeList) {
padding = new ElkPadding(topLeftInsets.preciseHeight() + separatorLineHeight, topLeftInsets.preciseWidth(), borderSize.height(), topLeftInsets.preciseWidth());
} else {
// For container with VStack, the label of region occupied all the width so we have
// not to use insets
padding = new ElkPadding(topLeftInsets.preciseHeight(), borderSize.preciseWidth(), borderSize.preciseHeight(), borderSize.preciseWidth());
}
parentLayoutNode.setProperty(CoreOptions.PADDING, padding);
// no space around regions
parentLayoutNode.setProperty(CoreOptions.SPACING_NODE_NODE, 0d);
// no space around labels
parentLayoutNode.setProperty(CoreOptions.NODE_LABELS_PADDING, new ElkPadding());
// Strategy set to INTERACTIVE to keep order of children
parentLayoutNode.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.INTERACTIVE);
if (dde instanceof DNodeContainer) {
intermediateNode.setProperty(CoreOptions.PADDING, new ElkPadding(0, 0, 0, 0));
intermediateNode.setProperty(CoreOptions.SPACING_NODE_NODE, 0d);
intermediateNode.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.INTERACTIVE);
}
intermediateNode.setProperty(CoreOptions.NODE_LABELS_PADDING, new ElkPadding());
} else if (dde instanceof DNodeContainer) {
DNodeContainerExperimentalQuery query = new DNodeContainerExperimentalQuery((DNodeContainer) dde);
if (query.isHorizontaltackContainer()) {
} else if (query.isVerticalStackContainer()) {
// Add some additional layout option to its container to have "fit" layout result
Dimension topLeftInsets = GMFHelper.getContainerTopLeftInsets((Node) compartment.getNotationView(), true);
ElkPadding padding = new ElkPadding(topLeftInsets.preciseHeight(), topLeftInsets.preciseWidth(), topLeftInsets.preciseHeight(), topLeftInsets.preciseWidth());
parentLayoutNode.setProperty(CoreOptions.PADDING, padding);
// no space around regions
parentLayoutNode.setProperty(CoreOptions.SPACING_NODE_NODE, 0d);
// Strategy set to INTERACTIVE to keep order of children
parentLayoutNode.setProperty(LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.INTERACTIVE);
}
}
}
buildLayoutGraphRecursively(mapping, intermediateNode, compartment, elkTargetToOptionsOverrideMap);
}
}
// process a node, which may be a parent of ports, compartments,
// or other nodes
} else if (obj instanceof ShapeNodeEditPart) {
ShapeNodeEditPart childNodeEditPart = (ShapeNodeEditPart) obj;
if (editPartFilter.filter(childNodeEditPart)) {
ElkNode node = createNode(mapping, childNodeEditPart, parentLayoutNode, elkTargetToOptionsOverrideMap);
maxChildShadowBorderSize = Math.max(maxChildShadowBorderSize, ElkDiagramLayoutConnector.getShadowBorderSize(childNodeEditPart));
// process the child as new current edit part
buildLayoutGraphRecursively(mapping, node, childNodeEditPart, elkTargetToOptionsOverrideMap);
}
// process a label of the current node
} else if (obj instanceof IGraphicalEditPart) {
ElkLabel newNodeLabel = createNodeLabel(mapping, (IGraphicalEditPart) obj, currentEditPart, parentLayoutNode, elkTargetToOptionsOverrideMap);
if (newNodeLabel != null) {
parentLayoutNode.getLabels().add(newNodeLabel);
}
}
if (maxChildShadowBorderSize >= 0 && currentEditPart.getNotationView() instanceof Node && !(currentEditPart instanceof ResizableCompartmentEditPart)) {
// maxChildShadowBorderSize == 0 : There is at least one child, so we set insets of this container
// maxChildShadowBorderSize > 0 : There is at least one child with a shadow border, we add this
// border
// size to the insets to avoid potential scrollbar appearance during the layout application
// (org.eclipse.sirius.diagram.elk.GmfLayoutEditPolicy.addShapeLayout(GmfLayoutCommand, ElkShape,
// GraphicalEditPart, double)).
Dimension topLeftInsets = GMFHelper.getContainerTopLeftInsets((Node) currentEditPart.getNotationView(), true);
// We directly reuse the left inset to sets the bottom and right insets.
ElkPadding ei = new ElkPadding(topLeftInsets.preciseHeight(), topLeftInsets.preciseWidth() + maxChildShadowBorderSize, topLeftInsets.preciseWidth() + maxChildShadowBorderSize,
topLeftInsets.preciseWidth());
parentLayoutNode.setProperty(CoreOptions.PADDING, ei);
}
}
}
/**
* Create a node while building the layout graph.
*
* @param mapping
* the layout mapping
* @param nodeEditPart
* the node edit part
* @param parentElkNode
* the corresponding parent layout node
* @param elkTargetToOptionsOverrideMap
* a map of option targets to corresponding options.
* @return the created node
*/
protected ElkNode createNode(final LayoutMapping mapping, final IGraphicalEditPart nodeEditPart, final ElkNode parentElkNode,
Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
IFigure nodeFigure = nodeEditPart.getFigure();
ElkNode newNode = ElkGraphUtil.createNode(parentElkNode);
applyOptionsRelatedToElementTarget(newNode, elkTargetToOptionsOverrideMap);
// set location and size
Rectangle childAbsoluteBounds = getAbsoluteBounds(nodeFigure);
KVector containerAbsoluteLocation = new KVector();
ElkUtil.toAbsolute(containerAbsoluteLocation, parentElkNode);
newNode.setX(childAbsoluteBounds.x - containerAbsoluteLocation.x);
newNode.setY(childAbsoluteBounds.y - containerAbsoluteLocation.y);
// Remove shadow border size for container: Indeed, the edges and border nodes use the "internal figure bounds"
// (ie without shadow). So for ELK the shadow size must be removed. It will be added before applying the layout
// in org.eclipse.sirius.diagram.elk.GmfLayoutEditPolicy.addShapeLayout(GmfLayoutCommand, ElkShape,
// GraphicalEditPart, double).
double shadowBorderSize = ElkDiagramLayoutConnector.getShadowBorderSize(nodeEditPart);
newNode.setDimensions(childAbsoluteBounds.width - shadowBorderSize, childAbsoluteBounds.height - shadowBorderSize);
// useful to debug.
if (nodeEditPart instanceof AbstractDNodeListCompartmentEditPart || nodeEditPart instanceof AbstractDNodeContainerCompartmentEditPart) {
newNode.setIdentifier("Compartment");
} else if (((View) nodeEditPart.getModel()).getElement() instanceof DDiagramElement) {
newNode.setIdentifier(((DDiagramElement) ((View) nodeEditPart.getModel()).getElement()).getName());
}
// determine minimal size of the node
try {
Dimension minSize = nodeFigure.getMinimumSize();
newNode.setProperty(CoreOptions.NODE_SIZE_MINIMUM, new KVector(minSize.width, minSize.height));
} catch (SWTException exception) {
// getMinimumSize() can cause this exception when fonts are disposed
// for some reason;
// ignore exception and leave the default minimal size
}
if (nodeEditPart instanceof SiriusNoteEditPart || nodeEditPart instanceof SiriusTextEditPart) {
// For the Note and Text, we want to have a fixed size (defined by the user)
newNode.setProperty(CoreOptions.NODE_SIZE_CONSTRAINTS, SizeConstraint.fixed());
}
if (parentElkNode != null) {
parentElkNode.getChildren().add(newNode);
applyParentNodeOption(parentElkNode, elkTargetToOptionsOverrideMap);
}
mapping.getGraphMap().put(newNode, nodeEditPart);
// Create label for Node, not Container, with name inside the node, not on border
final EObject eObj = nodeEditPart.resolveSemanticElement();
if (eObj instanceof DNode && ((NodeStyle) ((DNode) eObj).getStyle()).getLabelPosition() == LabelPosition.NODE_LITERAL) {
ElkLabel newNodeLabel = createNodeLabel(mapping, nodeEditPart, (IGraphicalEditPart) nodeEditPart.getParent(), parentElkNode, elkTargetToOptionsOverrideMap);
if (newNodeLabel != null) {
newNode.getLabels().add(newNodeLabel);
}
}
// store all the connections to process them later
addConnections(mapping, nodeEditPart);
return newNode;
}
/**
* Determines the insets for a parent figure, relative to the given child. Subclasses may override this if the
* generic insets calculation does not work.
*
* @param parent
* the figure of a parent edit part
* @param child
* the figure of a child edit part
* @return the insets to add to the relative coordinates of the child
*/
protected Insets calcSpecificInsets(final IFigure parent, final IFigure child) {
Insets result = new Insets(0);
IFigure currentChild = child;
IFigure currentParent = child.getParent();
Point coordsToAdd = null;
boolean isRelative = false;
// follow the chain of parents in the figure hierarchy up to the given
// parent figure
while (currentChild != parent && currentParent != null) {
if (currentParent.isCoordinateSystem()) {
// the content of the current parent is relative to that
// figure's position
isRelative = true;
result.add(currentParent.getInsets());
if (coordsToAdd != null) {
// add the position of the previous parent with local
// coordinate system
result.left += coordsToAdd.x;
result.top += coordsToAdd.y;
}
coordsToAdd = currentParent.getBounds().getLocation();
} else if (currentParent == parent && coordsToAdd != null) {
// we found the top parent, and it does not have local
// coordinate system,
// so subtract the parent's coordinates from the previous
// parent's position
Point parentCoords = parent.getBounds().getLocation();
result.left += coordsToAdd.x - parentCoords.x;
result.top += coordsToAdd.y - parentCoords.y;
}
currentChild = currentParent;
currentParent = currentChild.getParent();
}
if (!isRelative) {
// there is no local coordinate system, so just subtract the
// coordinates
Rectangle parentBounds = parent.getBounds();
currentParent = child.getParent();
Rectangle containerBounds = currentParent.getBounds();
result.left = containerBounds.x - parentBounds.x;
result.top = containerBounds.y - parentBounds.y;
}
// In theory it would be better to get the bottom and right insets from
// the size.
// However, due to the unpredictability of Draw2D layout managers, this
// leads to
// bad results in many cases, so a fixed insets value is more stable.
result.right = result.left;
result.bottom = result.left;
return result;
}
/**
* Create a port while building the layout graph.
*
* @param mapping
* the layout mapping
* @param portEditPart
* the port edit part
* @param nodeEditPart
* the parent node edit part
* @param elknode
* the corresponding layout node
* @param elkTargetToOptionsOverrideMap
* a map of option targets to corresponding options.
* @return the created port
*/
protected ElkPort createPort(final LayoutMapping mapping, final AbstractBorderItemEditPart portEditPart, final IGraphicalEditPart nodeEditPart, final ElkNode elknode,
Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
ElkPort port = ElkGraphUtil.createPort(elknode);
applyOptionsRelatedToElementTarget(port, elkTargetToOptionsOverrideMap);
// set the port's layout, relative to the node position
Rectangle portBounds = getAbsoluteBounds(portEditPart.getFigure());
Rectangle nodeBounds = getAbsoluteBounds(nodeEditPart.getFigure());
double xpos = portBounds.x - nodeBounds.x;
double ypos = portBounds.y - nodeBounds.y;
port.setLocation(xpos, ypos);
port.setDimensions(portBounds.width, portBounds.height);
// Compute the border node offset from Sirius
double borderNodeOffset = -IBorderItemOffsets.DEFAULT_OFFSET.preciseWidth();
final EObject eObj = portEditPart.resolveSemanticElement();
if (eObj instanceof DDiagramElement) {
if (new DDiagramElementQuery((DDiagramElement) eObj).isIndirectlyCollapsed()) {
borderNodeOffset = -IBorderItemOffsets.COLLAPSE_FILTER_OFFSET.preciseWidth();
}
}
port.setProperty(CoreOptions.PORT_BORDER_OFFSET, borderNodeOffset);
// We would set the modified flag to false here, but that doesn't exist
// anymore
mapping.getGraphMap().put(port, portEditPart);
// store all the connections to process them later
addConnections(mapping, portEditPart);
// set the port label
for (Object portChildObj : portEditPart.getChildren()) {
if (portChildObj instanceof IGraphicalEditPart) {
IFigure labelFigure = ((IGraphicalEditPart) portChildObj).getFigure();
String text = null;
if (labelFigure instanceof WrappingLabel) {
text = ((WrappingLabel) labelFigure).getText();
} else if (labelFigure instanceof Label) {
text = ((Label) labelFigure).getText();
} else if (labelFigure instanceof SiriusWrapLabel) {
SiriusWrapLabel label = (SiriusWrapLabel) labelFigure;
text = label.getText();
}
if (text != null) {
ElkLabel portLabel = ElkGraphUtil.createLabel(port);
applyOptionsRelatedToElementTarget(portLabel, elkTargetToOptionsOverrideMap);
portLabel.setText(text);
mapping.getGraphMap().put(portLabel, portChildObj);
// set the port label's layout
Rectangle labelBounds = getAbsoluteBounds(labelFigure);
portLabel.setLocation(labelBounds.x - portBounds.x, labelBounds.y - portBounds.y);
try {
Dimension size = labelFigure.getPreferredSize();
portLabel.setDimensions(size.width, size.height);
} catch (SWTException exception) {
// ignore exception and leave the label size to (0, 0)
}
// We would set the modified flag to false here, but that
// doesn't exist anymore
}
}
}
return port;
}
/**
* Create a node label while building the layout graph.
*
* @param mapping
* the layout mapping
* @param labelEditPart
* the label edit part
* @param nodeEditPart
* the parent node edit part
* @param elknode
* the layout node for which the label is set
* @param elkTargetToOptionsOverrideMap
* a map of option targets to corresponding options.
* @return the created label
*/
protected ElkLabel createNodeLabel(final LayoutMapping mapping, final IGraphicalEditPart labelEditPart, final IGraphicalEditPart nodeEditPart, final ElkNode elknode,
Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
IFigure labelFigure;
// We do not handle label of Note or Text (we want a fixed size for them).
if (!(labelEditPart instanceof SiriusDescriptionCompartmentEditPart)) {
if (labelEditPart instanceof IAbstractDiagramNodeEditPart) {
labelFigure = ((IAbstractDiagramNodeEditPart) labelEditPart).getNodeLabel();
} else {
labelFigure = labelEditPart.getFigure();
}
String text = null;
if (labelFigure instanceof WrappingLabel) {
WrappingLabel wrappingLabel = (WrappingLabel) labelFigure;
text = wrappingLabel.getText();
} else if (labelFigure instanceof Label) {
Label label = (Label) labelFigure;
text = label.getText();
} else if (labelFigure instanceof SiriusWrapLabel) {
SiriusWrapLabel label = (SiriusWrapLabel) labelFigure;
text = label.getText();
}
if (text != null) {
ElkLabel label = ElkGraphUtil.createLabel(elknode);
applyOptionsRelatedToElementTarget(label, elkTargetToOptionsOverrideMap);
label.setText(text);
Rectangle labelBounds = getAbsoluteBounds(labelFigure);
Rectangle nodeBounds;
if (!(labelEditPart instanceof IAbstractDiagramNodeEditPart)) {
mapping.getGraphMap().put(label, labelEditPart);
nodeBounds = getAbsoluteBounds(nodeEditPart.getFigure());
} else {
nodeBounds = getAbsoluteBounds(labelEditPart.getFigure());
}
label.setLocation(labelBounds.x - nodeBounds.x, labelBounds.y - nodeBounds.y);
try {
// We use the preferred size and not the labelBounds to "reset" previous manual wrapping size
Dimension size = labelFigure.getPreferredSize();
label.setDimensions(size.width, size.height);
} catch (SWTException exception) {
// ignore exception and leave the label size to (0, 0)
}
// Set globally the location of the label according to the style
NodeLabelPlacement insideLabelPlacement = NodeLabelPlacement.INSIDE;
NodeLabelPlacement verticalNodeLabelPlacement = NodeLabelPlacement.V_TOP;
NodeLabelPlacement horizontalLabelPlacement = NodeLabelPlacement.H_CENTER;
// For ListElement, we force H_CENTER and V_CENTER to have good layout result (fit).
if (labelEditPart instanceof DNodeListElementEditPart) {
verticalNodeLabelPlacement = NodeLabelPlacement.V_CENTER;
} else {
EObject siriusObject;
if (labelEditPart instanceof IAbstractDiagramNodeEditPart) {
siriusObject = labelEditPart.resolveSemanticElement();
} else {
siriusObject = nodeEditPart.resolveSemanticElement();
}
boolean forcedValue = false;
if (siriusObject instanceof DDiagramElement) {
if (siriusObject instanceof DNodeContainer) {
if (new DDiagramElementContainerExperimentalQuery((DNodeContainer) siriusObject).isRegionInVerticalStack()) {
// Keep default value (OK for this kind of container)
forcedValue = true;
verticalNodeLabelPlacement = NodeLabelPlacement.V_CENTER;
} else if (new DDiagramElementContainerExperimentalQuery((DNodeContainer) siriusObject).isRegionInHorizontalStack()) {
forcedValue = true;
// verticalNodeLabelPlacement = NodeLabelPlacement.V_TOP;
// horizontalLabelPlacement = NodeLabelPlacement.H_CENTER;
}
}
if (!forcedValue) {
DDiagramElement dde = (DDiagramElement) siriusObject;
Style style = dde.getStyle();
if (style instanceof LabelStyle) {
LabelAlignment labelAlignment = ((LabelStyle) style).getLabelAlignment();
if (labelAlignment.equals(LabelAlignment.LEFT)) {
horizontalLabelPlacement = NodeLabelPlacement.H_LEFT;
} else if (labelAlignment.equals(LabelAlignment.RIGHT)) {
horizontalLabelPlacement = NodeLabelPlacement.H_RIGHT;
}
}
if (style instanceof NodeStyle) {
if (((NodeStyle) style).getLabelPosition().equals(LabelPosition.BORDER_LITERAL)) {
insideLabelPlacement = NodeLabelPlacement.OUTSIDE;
}
verticalNodeLabelPlacement = NodeLabelPlacement.V_CENTER;
}
}
}
}
EnumSet<NodeLabelPlacement> enumSet = EnumSet.of(insideLabelPlacement, horizontalLabelPlacement, verticalNodeLabelPlacement);
label.setProperty(CoreOptions.NODE_LABELS_PLACEMENT, enumSet);
return label;
}
}
return null;
}
/**
* Adds all target connections and connected connections to the list of connections that must be processed later.
*
* @param mapping
* the layout mapping
* @param editPart
* an edit part
*/
protected void addConnections(final LayoutMapping mapping, final IGraphicalEditPart editPart) {
for (Object targetConn : editPart.getTargetConnections()) {
if (targetConn instanceof ConnectionEditPart) {
ConnectionEditPart connectionEditPart = (ConnectionEditPart) targetConn;
if (editPartFilter.filter(connectionEditPart)) {
mapping.getProperty(CONNECTIONS).add(connectionEditPart);
addConnections(mapping, connectionEditPart);
}
}
}
}
/**
* Creates new edges and takes care of the labels for each connection identified in the
* {@code buildLayoutGraphRecursively} method.
*
* @param mapping
* the layout mapping
* @param elkTargetToOptionsOverrideMap
* a map of option targets to corresponding options.
*/
protected void processConnections(final LayoutMapping mapping, Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
Map<EReference, ElkEdge> reference2EdgeMap = new HashMap<>();
for (ConnectionEditPart connection : mapping.getProperty(CONNECTIONS)) {
boolean isOppositeEdge = false;
EdgeLabelPlacement edgeLabelPlacement = EdgeLabelPlacement.UNDEFINED;
ElkEdge edge;
// Check whether the edge belongs to an Ecore reference, which may
// have opposites.
// This is required for the layout of Ecore diagrams, since the bend
// points of
// opposite references are kept synchronized by the editor.
EObject modelObject = connection.getNotationView().getElement();
if (modelObject instanceof EReference) {
EReference reference = (EReference) modelObject;
edge = reference2EdgeMap.get(reference.getEOpposite());
if (edge != null) {
edgeLabelPlacement = EdgeLabelPlacement.TAIL;
isOppositeEdge = true;
} else {
edge = ElkGraphUtil.createEdge(null);
reference2EdgeMap.put(reference, edge);
}
} else {
edge = ElkGraphUtil.createEdge(null);
}
applyOptionsRelatedToElementTarget(edge, elkTargetToOptionsOverrideMap);
BiMap<Object, ElkGraphElement> inverseGraphMap = mapping.getGraphMap().inverse();
// find a proper source node and source port
ElkGraphElement sourceElem;
EditPart sourceObj = connection.getSource();
if (sourceObj instanceof ConnectionEditPart) {
sourceElem = inverseGraphMap.get(((ConnectionEditPart) sourceObj).getSource());
if (sourceElem == null) {
sourceElem = inverseGraphMap.get(((ConnectionEditPart) sourceObj).getTarget());
}
} else {
sourceElem = inverseGraphMap.get(sourceObj);
}
ElkConnectableShape sourceShape = null;
ElkPort sourcePort = null;
ElkNode sourceNode = null;
if (sourceElem instanceof ElkNode) {
sourceNode = (ElkNode) sourceElem;
sourceShape = sourceNode;
} else if (sourceElem instanceof ElkPort) {
sourcePort = (ElkPort) sourceElem;
sourceNode = sourcePort.getParent();
sourceShape = sourcePort;
} else {
continue;
}
// find a proper target node and target port
ElkGraphElement targetElem;
EditPart targetObj = connection.getTarget();
if (targetObj instanceof ConnectionEditPart) {
targetElem = inverseGraphMap.get(((ConnectionEditPart) targetObj).getTarget());
if (targetElem == null) {
targetElem = inverseGraphMap.get(((ConnectionEditPart) targetObj).getSource());
}
} else {
targetElem = inverseGraphMap.get(targetObj);
}
ElkConnectableShape targetShape = null;
ElkNode targetNode = null;
ElkPort targetPort = null;
if (targetElem instanceof ElkNode) {
targetNode = (ElkNode) targetElem;
targetShape = targetNode;
} else if (targetElem instanceof ElkPort) {
targetPort = (ElkPort) targetElem;
targetNode = targetPort.getParent();
targetShape = targetPort;
} else {
continue;
}
// calculate offset for edge and label coordinates
ElkNode edgeContainment = ElkGraphUtil.findLowestCommonAncestor(sourceNode, targetNode);
KVector offset = new KVector();
ElkUtil.toAbsolute(offset, edgeContainment);
if (!isOppositeEdge) {
// set source and target
edge.getSources().add(sourceShape);
edge.getTargets().add(targetShape);
// now that source and target are set, put the edge into the
// graph
edgeContainment.getContainedEdges().add(edge);
mapping.getGraphMap().put(edge, connection);
// store the current coordinates of the edge
setEdgeLayout(edge, connection, offset);
}
// process edge labels
processEdgeLabels(mapping, connection, edge, edgeLabelPlacement, offset, elkTargetToOptionsOverrideMap);
}
}
/**
* Stores the layout information of the given connection edit part into an edge layout.
*
* @param edge
* an edge layout
* @param connection
* a connection edit part
* @param offset
* offset to be subtracted from coordinates
*/
protected void setEdgeLayout(final ElkEdge edge, final ConnectionEditPart connection, final KVector offset) {
Connection figure = connection.getConnectionFigure();
PointList pointList = figure.getPoints();
// our edge will have exactly one edge section
ElkEdgeSection edgeSection = ElkGraphUtil.createEdgeSection(edge);
// source point
Point firstPoint = pointList.getPoint(0);
edgeSection.setStartX(firstPoint.x - offset.x);
edgeSection.setStartY(firstPoint.y - offset.y);
// bend points
for (int i = 1; i < pointList.size() - 1; i++) {
Point point = pointList.getPoint(i);
ElkGraphUtil.createBendPoint(edgeSection, point.x - offset.x, point.y - offset.y);
}
// target point
Point lastPoint = pointList.getPoint(pointList.size() - 1);
edgeSection.setEndX(lastPoint.x - offset.x);
edgeSection.setEndY(lastPoint.y - offset.y);
//
if (figure instanceof Shape) {
double currentSize = ((Shape) figure).getLineWidth();
if (currentSize != CoreOptions.EDGE_THICKNESS.getDefault()) {
edge.setProperty(CoreOptions.EDGE_THICKNESS, currentSize);
}
}
}
/**
* Process the labels of an edge.
*
* @param mapping
* the layout mapping
* @param connection
* the connection edit part
* @param edge
* the layout edge
* @param placement
* predefined placement for all labels, or {@code UNDEFINED} if the placement shall be derived from the
* edit part
* @param offset
* the offset for coordinates
* @param elkTargetToOptionsOverrideMap
* map of option targets to corresponding options.
*/
protected void processEdgeLabels(final LayoutMapping mapping, final ConnectionEditPart connection, final ElkEdge edge, final EdgeLabelPlacement placement, final KVector offset,
Map<LayoutOptionTarget, Set<LayoutOption>> elkTargetToOptionsOverrideMap) {
/*
* ars: source and target is exchanged when defining it in the gmfgen file. So if Emma sets a label to be placed
* as target on a connection, then the label will show up next to the source node in the diagram editor. So
* correct it here, very ugly.
*/
for (Object obj : connection.getChildren()) {
if (obj instanceof LabelEditPart) {
LabelEditPart labelEditPart = (LabelEditPart) obj;
IFigure labelFigure = labelEditPart.getFigure();
// Check if the label is visible in the first place
if (labelFigure == null || !labelFigure.isVisible()) {
continue;
}
Rectangle labelBounds = getAbsoluteBounds(labelFigure);
String labelText = null;
Dimension iconBounds = null;
if (labelFigure instanceof WrappingLabel) {
WrappingLabel wrappingLabel = (WrappingLabel) labelFigure;
labelText = wrappingLabel.getText();
if (wrappingLabel.getIcon() != null) {
iconBounds = new Dimension();
iconBounds.width = wrappingLabel.getIcon().getBounds().width + wrappingLabel.getIconTextGap();
iconBounds.height = wrappingLabel.getIcon().getBounds().height;
// Add more characters to the text for layouters that
// need the text to
// determine the label size.
labelText = "O " + labelText;
}
} else if (labelFigure instanceof Label) {
Label label = (Label) labelFigure;
labelText = label.getText();
if (label.getIcon() != null) {
iconBounds = label.getIconBounds().getSize();
iconBounds.width += label.getIconTextGap();
// Add more characters to the text for layouters that
// need the text to
// determine the label size.
labelText = "O " + labelText;
}
} else if (labelFigure instanceof SiriusWrapLabel) {
SiriusWrapLabel label = (SiriusWrapLabel) labelFigure;
labelText = label.getText();
if (label.getIcon() != null) {
iconBounds = label.getIconBounds().getSize();
iconBounds.width += label.getIconTextGap();
// Add more characters to the text for layouters that
// need the text to
// determine the label size.
labelText = "O " + labelText;
}
}
if (labelText != null && labelText.length() > 0) {
ElkLabel label = ElkGraphUtil.createLabel(edge);
applyOptionsRelatedToElementTarget(label, elkTargetToOptionsOverrideMap);
if (placement == EdgeLabelPlacement.UNDEFINED) {
switch (labelEditPart.getKeyPoint()) {
case ConnectionLocator.SOURCE:
label.setProperty(CoreOptions.EDGE_LABELS_PLACEMENT, EdgeLabelPlacement.HEAD);
break;
case ConnectionLocator.MIDDLE:
label.setProperty(CoreOptions.EDGE_LABELS_PLACEMENT, EdgeLabelPlacement.CENTER);
break;
case ConnectionLocator.TARGET:
label.setProperty(CoreOptions.EDGE_LABELS_PLACEMENT, EdgeLabelPlacement.TAIL);
break;
}
} else {
label.setProperty(CoreOptions.EDGE_LABELS_PLACEMENT, placement);
}
label.setLocation(labelBounds.x - offset.x, labelBounds.y - offset.y);
// The label width includes the icon width
label.setWidth(labelBounds.width);
label.setHeight(labelBounds.height);
// We would set the modified flag to false here, but that
// doesn't exist anymore
label.setText(labelText);
mapping.getGraphMap().put(label, labelEditPart);
} else {
// add the label to the mapping anyway so it is reset to its
// reference location
ElkLabel label = ElkGraphUtil.createLabel(null);
mapping.getGraphMap().put(label, labelEditPart);
}
}
}
}
public void layout(LayoutMapping layoutMapping) {
BasicProgressMonitor basicProgressMonitor = new BasicProgressMonitor(0);
graphLayoutEngine.layout(layoutMapping.getLayoutGraph(), basicProgressMonitor.subTask(1));
}
@Override
public void applyLayout(LayoutMapping mapping, IPropertyHolder settings) {
// not used
}
/**
* Not used by Sirius connector. Instead we are using
* {@link org.eclipse.sirius.diagram.elk.ElkDiagramLayoutConnector#buildLayoutGraph(IDDiagramEditPart, Object)}
*
* @see org.eclipse.elk.core.service.IDiagramLayoutConnector#buildLayoutGraph(org.eclipse.ui.IWorkbenchPart,
* java.lang.Object)
*/
@Override
public LayoutMapping buildLayoutGraph(IWorkbenchPart workbenchPart, Object diagramPart) {
return null;
}
}