blob: 73786b894ec865fb72f8fe52d5271a7a30c1c64b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2020 THALES GLOBAL SERVICES 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.diagram.ui.internal.refresh;
import java.io.File;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Path;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditor;
import org.eclipse.gmf.runtime.gef.ui.figures.NodeFigure;
import org.eclipse.gmf.runtime.notation.Bounds;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.LayoutConstraint;
import org.eclipse.gmf.runtime.notation.Location;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.RelativeBendpoints;
import org.eclipse.gmf.runtime.notation.Size;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.gmf.runtime.notation.datatype.RelativeBendpoint;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.sirius.common.tools.api.resource.FileProvider;
import org.eclipse.sirius.common.tools.api.util.StringUtil;
import org.eclipse.sirius.common.ui.tools.api.util.EclipseUIUtil;
import org.eclipse.sirius.diagram.AbstractDNode;
import org.eclipse.sirius.diagram.ContainerStyle;
import org.eclipse.sirius.diagram.DDiagram;
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.WorkspaceImage;
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.ui.business.api.query.NodeQuery;
import org.eclipse.sirius.diagram.ui.business.api.query.ViewQuery;
import org.eclipse.sirius.diagram.ui.business.internal.query.DNodeContainerQuery;
import org.eclipse.sirius.diagram.ui.business.internal.query.DNodeQuery;
import org.eclipse.sirius.diagram.ui.edit.api.part.AbstractDiagramElementContainerEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.AbstractDNodeContainerCompartmentEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeContainer2EditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeList2EditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListEditPart;
import org.eclipse.sirius.diagram.ui.internal.refresh.borderednode.CanonicalDBorderItemLocator;
import org.eclipse.sirius.diagram.ui.part.SiriusVisualIDRegistry;
import org.eclipse.sirius.diagram.ui.provider.DiagramUIPlugin;
import org.eclipse.sirius.diagram.ui.provider.Messages;
import org.eclipse.sirius.diagram.ui.tools.api.layout.LayoutUtils;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.base.Options;
import org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.AlphaDropShadowBorder;
import org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.IContainerLabelOffsets;
import org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.LabelBorderStyleIds;
import org.eclipse.sirius.ui.business.api.dialect.DialectEditor;
import org.eclipse.sirius.ui.business.api.session.IEditingSession;
import org.eclipse.sirius.ui.business.api.session.SessionUIManager;
import org.eclipse.sirius.ui.tools.api.color.VisualBindingManager;
import org.eclipse.sirius.viewpoint.BasicLabelStyle;
import org.eclipse.sirius.viewpoint.DSemanticDecorator;
import org.eclipse.sirius.viewpoint.description.style.LabelBorderStyleDescription;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IEditorPart;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
/**
* GMF Helper.
*
* @author edugueperoux
*/
public final class GMFHelper {
/**
* see org.eclipse.sirius.diagram.ui.internal.edit.parts. AbstractDNodeContainerCompartmentEditPart.DEFAULT_MARGIN
* the Y value is the DEFAULT_MARGIN + the InvisibleResizableCompartmentFigure top Inset (1px)
*/
private static Point CONTAINER_INSETS = new Point(AbstractDNodeContainerCompartmentEditPart.DEFAULT_MARGIN, IContainerLabelOffsets.LABEL_OFFSET);
/**
* The gap in pixels between the Label's icon and its text
* (org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.SiriusWrapLabel.getIconTextGap()).
*/
private static final int ICON_TEXT_GAP = 3;
private GMFHelper() {
// Helper to not instantiate
}
/**
* Get the absolute location relative to the origin (Diagram).
*
* @param node
* the GMF Node
*
* @return the absolute location of the node relative to the origin (Diagram)
*/
public static Point getAbsoluteLocation(Node node) {
return getAbsoluteLocation(node, false);
}
/**
* Get the absolute location relative to the origin (Diagram).
*
* @param node
* the GMF Node
* @param insetsAware
* true to consider the draw2D figures insets. <strong>Warning:</strong> Those insets are based on the
* current Sirius editParts and could become wrong if a developer customizes them.
*
* @return the absolute location of the node relative to the origin (Diagram)
*/
public static Point getAbsoluteLocation(Node node, boolean insetsAware) {
Node currentNode = node;
Point absoluteNodeLocation = getLocation(currentNode);
if (currentNode.eContainer() instanceof Node) {
currentNode = (Node) currentNode.eContainer();
Point parentNodeLocation = getAbsoluteLocation(currentNode, insetsAware);
absoluteNodeLocation.translate(parentNodeLocation);
if (insetsAware) {
translateWithInsets(absoluteNodeLocation, node);
}
}
return absoluteNodeLocation;
}
/**
* Return the top-left insets of the container of this <code>node</code>. The insets also considers its border.
*
* @param node
* The current node
* @param searchFirstParentContainer
* true to call recursively until finding a Node container, {@link NodeQuery#isContainer()}, false
* otherwise
* @return the top-left insets of the container of this <code>node</code>
*/
public static Dimension getContainerTopLeftInsets(Node node, boolean searchFirstParentContainer) {
Dimension result = new Dimension(0, 0);
EObject nodeContainer = node.eContainer();
if (nodeContainer instanceof Node) {
Node parentNode = (Node) nodeContainer;
NodeQuery nodeQuery = new NodeQuery(parentNode);
if (nodeQuery.isContainer()) {
EObject element = parentNode.getElement();
if (element instanceof DDiagramElementContainer) {
DDiagramElementContainer ddec = (DDiagramElementContainer) element;
// RegionContainer do not have containers insets
if (ddec instanceof DNodeContainer) {
if (new DNodeContainerExperimentalQuery((DNodeContainer) ddec).isRegionContainer() || hasFullLabelBorder(ddec)) {
result.setHeight(CONTAINER_INSETS.y + getLabelSize(parentNode) + AbstractDiagramElementContainerEditPart.DEFAULT_SPACING);
} else {
result.setWidth(CONTAINER_INSETS.x);
result.setHeight(CONTAINER_INSETS.y);
}
}
Dimension borderSize = getBorderSize(ddec);
result.setWidth(result.width() + borderSize.width());
result.setHeight(result.height() + borderSize.height());
}
} else if (searchFirstParentContainer) {
result = getContainerTopLeftInsets(parentNode, searchFirstParentContainer);
}
}
return result;
}
/**
* Return the top-left insets of the container of this <code>node</code> that is after the label. The insets also
* considers its border.
*
* @param node
* The current node
* @param searchFirstParentContainer
* true to call recursively until finding a Node container, {@link NodeQuery#isContainer()}, false
* otherwise
* @return the top-left insets of the container of this <code>node</code>
*/
public static Dimension getContainerTopLeftInsetsAfterLabel(Node node, boolean searchFirstParentContainer) {
Dimension result = new Dimension(0, 0);
EObject nodeContainer = node.eContainer();
if (nodeContainer instanceof Node) {
Node parentNode = (Node) nodeContainer;
NodeQuery nodeQuery = new NodeQuery(parentNode);
if (nodeQuery.isContainer()) {
EObject element = parentNode.getElement();
if (element instanceof DDiagramElementContainer) {
result.setWidth(CONTAINER_INSETS.x);
result.setHeight(CONTAINER_INSETS.y);
Dimension borderSize = getBorderSize((DDiagramElementContainer) element);
result.setWidth(result.width() + borderSize.width());
result.setHeight(result.height() + borderSize.height());
}
} else if (searchFirstParentContainer) {
result = getContainerTopLeftInsets(parentNode, searchFirstParentContainer);
}
}
return result;
}
/**
* Get the border size of the <code>ddec</code> ({@link DDiagramElementContainer}).
*
* @param ddec
* The {@link DDiagramElementContainer}
* @return the border size of the diagram element container.
*/
public static Dimension getBorderSize(DDiagramElementContainer ddec) {
Dimension result = new Dimension(0, 0);
ContainerStyle containerStyle = ddec.getOwnedStyle();
int borderSize = containerStyle.getBorderSize().intValue();
DDiagramElementContainerExperimentalQuery regionQuery = new DDiagramElementContainerExperimentalQuery(ddec);
if (regionQuery.isRegionInHorizontalStack()) {
result.setWidth(isFirstRegion(ddec) ? 0 : borderSize);
result.setHeight(1);
} else if (regionQuery.isRegionInVerticalStack()) {
result.setWidth(1);
result.setHeight(isFirstRegion(ddec) ? 1 : borderSize);
} else {
result.setWidth(borderSize);
result.setHeight(borderSize);
}
return result;
}
/**
* Shift the current node absolute location by the container insets.
*
* @param locationToTranslate
* the current computed location that will be translated by the container insets.
* @param currentNode
* the current node for which we translate location. We do not change the currentNode bounds.
*/
private static void translateWithInsets(Point locationToTranslate, Node currentNode) {
NodeQuery nodeQuery = new NodeQuery(currentNode);
// bordered node are not concerned by those insets.
if (!nodeQuery.isBorderedNode()) {
locationToTranslate.translate(getContainerTopLeftInsets(currentNode, false));
}
}
private static boolean hasFullLabelBorder(DDiagramElementContainer ddec) {
Option<LabelBorderStyleDescription> labelBorderStyle = new DDiagramElementContainerExperimentalQuery(ddec).getLabelBorderStyle();
return labelBorderStyle.some() && LabelBorderStyleIds.LABEL_FULL_BORDER_STYLE_FOR_CONTAINER_ID.equals(labelBorderStyle.get().getId());
}
private static int getLabelSize(Node parentNode) {
int labelSize = 0;
for (Node child : Iterables.filter(parentNode.getVisibleChildren(), Node.class)) {
if (new ViewQuery(child).isForNameEditPart()) {
// TODO Compute the real label height
// It depends on the font size
// It might require to set the layout constraint of the label
// GMF node which will not be used by the
// ConstrainedToolbarLayout to locate the label but might be
// usefull to store the value in the model.
labelSize = 16;
break;
}
}
return labelSize;
}
private static boolean isFirstRegion(DDiagramElementContainer ddec) {
EObject potentialRegionContainer = ddec.eContainer();
if (potentialRegionContainer instanceof DNodeContainer) {
Iterable<DDiagramElementContainer> regions = Iterables.filter(((DNodeContainer) potentialRegionContainer).getOwnedDiagramElements(), DDiagramElementContainer.class);
return !Iterables.isEmpty(regions) && ddec == Iterables.getFirst(regions, null);
}
return false;
}
/**
* Shift the current node absolute bounds location by the container insets.
*
* @param boundsToTranslate
* the current computed bounds that will be translated by the container insets.
* @param currentNode
* the current node for which we translate bounds. We do not change the currentNode bounds.
*/
private static void translateWithInsets(Rectangle boundsToTranslate, Node currentNode) {
Point location = boundsToTranslate.getLocation();
translateWithInsets(location, currentNode);
boundsToTranslate.setLocation(location);
}
/**
* Compute the location of a GMF node.
*
* @param node
* the node whose location to compute.
* @return the location of the node.
*/
public static Point getLocation(Node node) {
Point location = new Point(0, 0);
LayoutConstraint layoutConstraint = node.getLayoutConstraint();
if (layoutConstraint instanceof Bounds) {
Bounds gmfBounds = (Bounds) layoutConstraint;
location.x = gmfBounds.getX();
location.y = gmfBounds.getY();
// manage location of bordered node with closest side
if (node.getElement() instanceof DNode && node.getElement().eContainer() instanceof AbstractDNode) {
DNode dNode = (DNode) node.getElement();
AbstractDNode parentAbstractDNode = (AbstractDNode) dNode.eContainer();
if (parentAbstractDNode.getOwnedBorderedNodes().contains(dNode)) {
Node parentNode = (Node) node.eContainer();
LayoutConstraint parentLayoutConstraint = parentNode.getLayoutConstraint();
if (parentLayoutConstraint instanceof Bounds) {
Bounds parentBounds = (Bounds) parentLayoutConstraint;
int position = CanonicalDBorderItemLocator.findClosestSideOfParent(new Rectangle(gmfBounds.getX(), gmfBounds.getY(), gmfBounds.getWidth(), gmfBounds.getHeight()),
new Rectangle(parentBounds.getX(), parentBounds.getY(), parentBounds.getWidth(), parentBounds.getHeight()));
updateLocation(location, position, parentBounds, gmfBounds);
}
}
}
}
return location;
}
private static void updateLocation(Point location, int position, Bounds parentBounds, Bounds gmfBounds) {
switch (position) {
case PositionConstants.NORTH:
case PositionConstants.SOUTH:
if (location.x == 0) {
location.x += (parentBounds.getWidth() - gmfBounds.getWidth()) / 2;
}
break;
case PositionConstants.WEST:
case PositionConstants.EAST:
if (location.y == 0) {
location.y += (parentBounds.getHeight() - gmfBounds.getHeight()) / 2;
}
break;
default:
break;
}
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param node
* the GMF Node
*
* @return the absolute bounds of the node relative to the origin (Diagram)
*/
public static Rectangle getAbsoluteBounds(Node node) {
return getAbsoluteBounds(node, false, false);
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param node
* the GMF Node
* @param insetsAware
* true to consider the draw2D figures insets. <strong>Warning:</strong> Those insets are based on the
* current Sirius editParts and could become wrong if a developer customizes them.
*
* @return the absolute bounds of the node relative to the origin (Diagram)
*/
public static Rectangle getAbsoluteBounds(Node node, boolean insetsAware) {
return getAbsoluteBounds(node, false, false);
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param node
* the GMF Node
* @param insetsAware
* true to consider the draw2D figures insets. <strong>Warning:</strong> Those insets are based on the
* current Sirius editParts and could become wrong if a developer customizes them.
* @param boxForConnection
* true if we want to have the bounds used to compute connection anchor from source or target, false
* otherwise
* @return the absolute bounds of the node relative to the origin (Diagram)
*/
public static Rectangle getAbsoluteBounds(Node node, boolean insetsAware, boolean boxForConnection) {
Node currentNode = node;
Rectangle absoluteNodeBounds = getBounds(currentNode, false, false, boxForConnection);
if (currentNode.eContainer() instanceof Node) {
currentNode = (Node) currentNode.eContainer();
Point parentNodeLocation = getAbsoluteLocation(currentNode, insetsAware);
absoluteNodeBounds = absoluteNodeBounds.getTranslated(parentNodeLocation);
if (insetsAware) {
translateWithInsets(absoluteNodeBounds, node);
}
}
return absoluteNodeBounds;
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param edge
* the GMF Node
*
* @return the absolute bounds of the edge relative to the origin (Diagram)
*/
public static Option<Rectangle> getAbsoluteBounds(Edge edge) {
return getAbsoluteBounds(edge, false, false);
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param edge
* the GMF Node
* @param insetsAware
* true to consider the draw2D figures insets. <strong>Warning:</strong> Those insets are based on the
* current Sirius editParts and could become wrong if a developer customizes them.
* @param boxForConnection
* true if we want to have the bounds used to compute connection anchor from source or target, false
* otherwise
*
* @return the absolute bounds of the edge relative to the origin (Diagram)
*/
public static Option<Rectangle> getAbsoluteBounds(Edge edge, boolean insetsAware, boolean boxForConnection) {
// Workaround for canonical refresh about edge on edge
Option<Rectangle> optionalSourceBounds = getAbsoluteBounds(edge.getSource(), insetsAware);
Option<Rectangle> optionalTargetBounds = getAbsoluteBounds(edge.getTarget(), insetsAware);
if (optionalSourceBounds.some() && optionalTargetBounds.some()) {
return Options.newSome(optionalSourceBounds.get().union(optionalTargetBounds.get()));
}
return Options.newNone();
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param view
* the GMF Node or Edge
*
* @return an optional absolute bounds of the node or edge relative to the origin (Diagram)
*/
public static Option<Rectangle> getAbsoluteBounds(View view) {
return getAbsoluteBounds(view, false);
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param view
* the GMF Node or Edge
* @param insetsAware
* true to consider the draw2D figures insets. <strong>Warning:</strong> Those insets are based on the
* current Sirius editParts and could become wrong if a developer customizes them.
*
* @return an optional absolute bounds of the node or edge relative to the origin (Diagram)
*/
public static Option<Rectangle> getAbsoluteBounds(View view, boolean insetsAware) {
return getAbsoluteBounds(view, insetsAware, false);
}
/**
* Get the absolute bounds relative to the origin (Diagram).
*
* @param view
* the GMF Node or Edge
* @param insetsAware
* true to consider the draw2D figures insets. <strong>Warning:</strong> Those insets are based on the
* current Sirius editParts and could become wrong if a developer customizes them.
* @param boxForConnection
* true if we want to have the bounds used to compute connection anchor from source or target, false
* otherwise
*
* @return an optional absolute bounds of the node or edge relative to the origin (Diagram)
*/
public static Option<Rectangle> getAbsoluteBounds(View view, boolean insetsAware, boolean boxForConnection) {
Option<Rectangle> result = Options.newNone();
if (view instanceof Node) {
result = Options.newSome(getAbsoluteBounds((Node) view, insetsAware, boxForConnection));
} else if (view instanceof Edge) {
result = getAbsoluteBounds((Edge) view, insetsAware, boxForConnection);
}
return result;
}
/**
* Compute the bounds of a GMF node.
*
* @param node
* the node whose bounds to compute.
* @return the bounds of the node.
*/
public static Rectangle getBounds(Node node) {
return getBounds(node, false);
}
/**
* Compute the bounds of a GMF node.
*
* @param node
* the node whose bounds to compute.
* @param useFigureForAutoSizeConstraint
* true to use figure for auto size constraint
* @return the bounds of the node.
*/
public static Rectangle getBounds(Node node, boolean useFigureForAutoSizeConstraint) {
return getBounds(node, useFigureForAutoSizeConstraint, false);
}
/**
* Compute the bounds of a GMF node.
*
* @param node
* the node whose bounds to compute.
* @param useFigureForAutoSizeConstraint
* true to use figure for auto size constraint
* @param forceFigureAutoSize
* if useFigureForAutoSizeConstraint and if the found edit part supports it, force auto size and validate
* the parent to get the auto-sized dimension (during auto-size for example)
* @return the bounds of the node.
*/
public static Rectangle getBounds(Node node, boolean useFigureForAutoSizeConstraint, boolean forceFigureAutoSize) {
return getBounds(node, useFigureForAutoSizeConstraint, false, false);
}
/**
* Compute the bounds of a GMF node.
*
* @param node
* the node whose bounds to compute.
* @param useFigureForAutoSizeConstraint
* true to use figure for auto size constraint
* @param forceFigureAutoSize
* if useFigureForAutoSizeConstraint and if the found edit part supports it, force auto size and validate
* the parent to get the auto-sized dimension (during auto-size for example)
* @param boxForConnection
* true if we want to have the bounds used to compute connection anchor from source or target, false
* otherwise
* @return the bounds of the node.
*/
public static Rectangle getBounds(Node node, boolean useFigureForAutoSizeConstraint, boolean forceFigureAutoSize, boolean boxForConnection) {
PrecisionRectangle bounds = new PrecisionRectangle(0, 0, 0, 0);
LayoutConstraint layoutConstraint = node.getLayoutConstraint();
EObject element = node.getElement();
if (element instanceof AbstractDNode) {
AbstractDNode abstractDNode = (AbstractDNode) element;
if (layoutConstraint instanceof Location) {
bounds.x = ((Location) layoutConstraint).getX();
bounds.y = ((Location) layoutConstraint).getY();
}
if (layoutConstraint instanceof Size) {
bounds.width = ((Size) layoutConstraint).getWidth();
bounds.height = ((Size) layoutConstraint).getHeight();
} else {
bounds.width = -1;
bounds.height = -1;
}
if (new ViewQuery(node).isForNameEditPart()) {
if (abstractDNode.getName() == null || abstractDNode.getName().length() == 0) {
if (bounds.width == -1) {
bounds.width = 0;
}
if (bounds.height == -1) {
bounds.height = 0;
}
} else {
// Make a default size for label (this size is purely an average estimate)
replaceAutoSize(node, bounds, useFigureForAutoSizeConstraint, getLabelDimension(node, new Dimension(50, 20)));
}
} else {
replaceAutoSize(node, bounds, useFigureForAutoSizeConstraint, null);
}
if (boxForConnection) {
// Remove the shadow border size (as it is done in SlidableAnchor.getBox() that calls
// HandleBounds.getHandleBounds() (for example
// org.eclipse.gmf.runtime.gef.ui.figures.NodeFigure.getHandleBounds())
// This insets corresponds to insets of {@link
// org.eclipse.sirius.diagram.ui.tools.api.figure.AlphaDropShadowBorder#getTransparentInsets()}.
double shadowBorderSize = getShadowBorderSize(node);
bounds.width -= shadowBorderSize;
bounds.height -= shadowBorderSize;
}
}
return bounds;
}
/**
* 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(Node node) {
double shadowBorderSize = 0;
if (isShadowBorderNeeded(node)) {
shadowBorderSize = AlphaDropShadowBorder.getDefaultShadowSize();
}
return shadowBorderSize;
}
/**
* Shadow border is needed for all container except for regions or workspace image styles. These can have a
* non-rectangular contour and transparent zones which should be kept as is.
*
* @return false for regions and workspace images, true otherwise.
*/
public static boolean isShadowBorderNeeded(Node node) {
boolean needShadowBorder = false;
EObject element = node.getElement();
if (element instanceof DDiagramElementContainer) {
DDiagramElementContainer ddec = (DDiagramElementContainer) element;
needShadowBorder = !(new DDiagramElementContainerExperimentalQuery(ddec).isRegion() || ddec.getOwnedStyle() instanceof WorkspaceImage);
}
return needShadowBorder;
}
/**
* This method replace the -1x-1 size with a more realistic size. This size is probably not exactly the same as in
* draw2d but much closer that -1x-1.
*
* @param node
* The GMF node with the auto-size
* @param bounds
* The bounds with auto-size to replace.
* @param useFigureForAutoSizeConstraint
* true to use draw2d figure to get size
* @param providedDefaultSize
* The size used for creation for this kind of <code>node</code>. It is the minimum size.
*/
private static void replaceAutoSize(Node node, Rectangle bounds, boolean useFigureForAutoSizeConstraint, Dimension providedDefaultSize) {
if (bounds.width == -1 || bounds.height == -1) {
Dimension defaultSize = providedDefaultSize;
if (providedDefaultSize == null) {
// if there is no default size, we compute it from the given
// node.
EObject element = node.getElement();
if (element instanceof AbstractDNode) {
defaultSize = getDefaultSize((AbstractDNode) element);
}
}
if (useFigureForAutoSizeConstraint) {
// Use the figure (if founded) to set width and height
// instead of (-1, -1)
Option<GraphicalEditPart> optionalTargetEditPart = getGraphicalEditPart(node);
// CHECKSTYLE:OFF
if (optionalTargetEditPart.some()) {
GraphicalEditPart graphicalEditPart = optionalTargetEditPart.get();
if (graphicalEditPart instanceof AbstractDiagramElementContainerEditPart) {
((AbstractDiagramElementContainerEditPart) graphicalEditPart).forceFigureAutosize();
((GraphicalEditPart) graphicalEditPart.getParent()).getFigure().validate();
}
Rectangle figureBounds = graphicalEditPart.getFigure().getBounds();
if (bounds.width == -1) {
bounds.width = figureBounds.width;
}
if (bounds.height == -1) {
bounds.height = figureBounds.height;
}
} else {
// Diagram editor might be initializing and there is no edit
// part yet. For regions we might retrieve the previous
// known constraints in the GMF model by looking into the
// GMF location of the next region as they were computed
// from the location and size of the current region.
lookForNextRegionLocation(bounds, node);
}
// CHECKSTYLE:ON
} else {
// Compute the bounds of all children and use the lowest
// one (y+height) for height and the rightmost one
// (x+width) for width.
Point bottomRight = getBottomRight(node);
if (bounds.width == -1) {
if (bottomRight.x > defaultSize.width) {
bounds.width = bottomRight.x;
} else {
bounds.width = defaultSize.width;
}
}
if (bounds.height == -1) {
if (bottomRight.y > defaultSize.height) {
bounds.height = bottomRight.y;
} else {
bounds.height = defaultSize.height;
}
}
}
if (bounds.width == -1) {
bounds.width = defaultSize.width;
}
if (bounds.height == -1) {
bounds.height = defaultSize.height;
}
}
}
private static void lookForNextRegionLocation(Rectangle bounds, Node node) {
EObject element = node.getElement();
if (element instanceof DDiagramElementContainer && node.eContainer() instanceof Node) {
DDiagramElementContainer ddec = (DDiagramElementContainer) element;
DDiagramElementContainerExperimentalQuery query = new DDiagramElementContainerExperimentalQuery(ddec);
boolean isRegion = query.isRegion();
EList children = ((Node) node.eContainer()).getChildren();
int currentIndex = children.indexOf(node);
if (!(currentIndex != 0 && bounds.equals(new Rectangle(0, 0, -1, -1)))) {
// We are not in the case of a new region insertion (in this
// case, we use the default size)
int nextIndex = currentIndex + 1;
if (isRegion && nextIndex != 0 && nextIndex < children.size() && children.get(nextIndex) instanceof Node) {
Node nextNode = (Node) children.get(nextIndex);
int visualID = SiriusVisualIDRegistry.getVisualID(nextNode.getType());
if (DNodeContainer2EditPart.VISUAL_ID == visualID || DNodeListEditPart.VISUAL_ID == visualID || DNodeList2EditPart.VISUAL_ID == visualID) {
// DNodeContainerEditPart.VISUAL_ID == visualID is not
// checked as a region cannot be a
// DNodeContainerEditPart as it is directly contained by
// the diagram part.
LayoutConstraint layoutConstraint = nextNode.getLayoutConstraint();
if (layoutConstraint instanceof Location) {
Location nextLocation = (Location) layoutConstraint;
// Update only the parent stack direction if some
// layout has already been done.
if (bounds.width == -1 && query.isRegionInHorizontalStack() && nextLocation.getX() != 0) {
bounds.width = nextLocation.getX() - bounds.x;
}
if (bounds.height == -1 && query.isRegionInVerticalStack() && nextLocation.getY() != 0) {
bounds.height = nextLocation.getY() - bounds.y;
}
}
}
}
}
}
}
/**
* Returns a new Point representing the bottom right point of all bounds of children of this Node. Useful for Node
* with size of -1x-1 to be more accurate (but it is still not necessarily the same size that draw2d).
*
* @param node
* the node whose bottom right corner is to compute.
*
* @return Point at the bottom right of the rectangle
*/
public static Point getBottomRight(Node node) {
int right = 0;
int bottom = 0;
for (Iterator<Node> children = Iterators.filter(node.getChildren().iterator(), Node.class); children.hasNext(); /* */) {
Node child = children.next();
// The bordered nodes is ignored
if (!(new NodeQuery(node).isBorderedNode())) {
Rectangle bounds = getBounds(child);
Point bottomRight = bounds.getBottomRight();
if (bottomRight.x > right) {
right = bottomRight.x;
}
if (bottomRight.y > bottom) {
bottom = bottomRight.y;
}
}
}
return new Point(right, bottom);
}
private static Dimension getDefaultSize(AbstractDNode abstractDNode) {
Dimension defaultSize = new Dimension(-1, -1);
if (abstractDNode instanceof DNode) {
defaultSize = new DNodeQuery((DNode) abstractDNode).getDefaultDimension();
} else if (abstractDNode instanceof DNodeContainer) {
defaultSize = new DNodeContainerQuery((DNodeContainer) abstractDNode).getDefaultDimension();
} else if (abstractDNode instanceof DNodeList) {
defaultSize = LayoutUtils.NEW_DEFAULT_CONTAINER_DIMENSION;
}
return defaultSize;
}
/**
* Return an option with the editPart corresponding to the <code>view</code> in the current diagram or an empty
* Option if there is no corresponding editPart.
*
* @param view
* The view element that is searched
* @return The optional corresponding edit part.
*/
public static Option<GraphicalEditPart> getGraphicalEditPart(View view) {
if (view != null) {
Diagram gmfDiagram = view.getDiagram();
// Try the active editor first (most likely case in practice)
IEditorPart editor = EclipseUIUtil.getActiveEditor();
if (isEditorFor(editor, gmfDiagram)) {
return getGraphicalEditPart(view, (DiagramEditor) editor);
} else if (gmfDiagram.getElement() instanceof DDiagram) {
// Otherwise check all active Sirius editors
for (IEditingSession uiSession : SessionUIManager.INSTANCE.getUISessions()) {
DialectEditor dialectEditor = uiSession.getEditor((DDiagram) gmfDiagram.getElement());
if (isEditorFor(dialectEditor, gmfDiagram)) {
return getGraphicalEditPart(view, (DiagramEditor) dialectEditor);
}
}
}
}
return Options.<GraphicalEditPart> newNone();
}
private static boolean isEditorFor(IEditorPart editor, Diagram diagram) {
return editor instanceof DiagramEditor && ((DiagramEditor) editor).getDiagram() == diagram;
}
/**
* Return an option with the editPart corresponding to the <code>view</code> in the current diagram or an empty
* Option if there is no corresponding editPart.
*
* @param view
* The view element that is searched
* @param editor
* the editor where looking for the edit part.
* @return The optional corresponding edit part.
*/
public static Option<GraphicalEditPart> getGraphicalEditPart(View view, DiagramEditor editor) {
Option<GraphicalEditPart> result = Options.newNone();
final Map<?, ?> editPartRegistry = editor.getDiagramGraphicalViewer().getEditPartRegistry();
final EditPart targetEditPart = (EditPart) editPartRegistry.get(view);
if (targetEditPart instanceof GraphicalEditPart) {
result = Options.newSome((GraphicalEditPart) targetEditPart);
}
return result;
}
/**
* Get the points list computed from GMF bendpoints according to source side for the <code>edgeEditPart</code>.
*
* @param edgeEditPart
* The concerned edge edit part.
* @return Points list
* @throws IllegalArgumentException
* when the edgeEditPart is not as expected
*/
public static List<Point> getPointsFromSource(ConnectionEditPart edgeEditPart) throws IllegalArgumentException {
if (edgeEditPart.getModel() instanceof Edge && edgeEditPart.getFigure() instanceof Connection) {
List<Point> result = new ArrayList<>();
Edge gmfEdge = (Edge) edgeEditPart.getModel();
Connection connectionFigure = (Connection) edgeEditPart.getFigure();
Point srcAnchorLoc = connectionFigure.getSourceAnchor().getReferencePoint();
connectionFigure.translateToRelative(srcAnchorLoc);
RelativeBendpoints bp = (RelativeBendpoints) gmfEdge.getBendpoints();
for (int i = 0; i < bp.getPoints().size(); i++) {
RelativeBendpoint rbp = (RelativeBendpoint) bp.getPoints().get(i);
Point fromSrc = srcAnchorLoc.getTranslated(rbp.getSourceX(), rbp.getSourceY());
result.add(fromSrc);
}
return result;
}
throw new IllegalArgumentException(Messages.GMFHelper_invalidEdgeModelAndFigure);
}
/**
* Get the points list computed from GMF bendpoints according to target side for the <code>edgeEditPart</code>.
*
* @param edgeEditPart
* The concerned edge edit part.
* @return Points list
* @throws IllegalArgumentException
* when the edgeEditPart is not as expected
*/
public static List<Point> getPointsFromTarget(ConnectionEditPart edgeEditPart) throws IllegalArgumentException {
if (edgeEditPart.getModel() instanceof Edge && edgeEditPart.getFigure() instanceof Connection) {
List<Point> result = new ArrayList<>();
Edge gmfEdge = (Edge) edgeEditPart.getModel();
Connection connectionFigure = (Connection) edgeEditPart.getFigure();
Point tgtAnchorLoc = connectionFigure.getTargetAnchor().getReferencePoint();
connectionFigure.translateToRelative(tgtAnchorLoc);
RelativeBendpoints bp = (RelativeBendpoints) gmfEdge.getBendpoints();
for (int i = 0; i < bp.getPoints().size(); i++) {
RelativeBendpoint rbp = (RelativeBendpoint) bp.getPoints().get(i);
Point fromTgt = tgtAnchorLoc.getTranslated(rbp.getTargetX(), rbp.getTargetY());
result.add(fromTgt);
}
return result;
}
throw new IllegalArgumentException(Messages.GMFHelper_invalidEdgeModelAndFigure);
}
/**
* Compute the size of a label. This method uses "UI data" to retrieve the label size. It must be called in UI
* thread to have right result, otherwise <code>defaultDimension</code> will be used.
*
* @param node
* the node, corresponding to a DNodeNameEditPart, whose size to compute.
* @param defaultDimension
* the default dimension to use if it is not possible to get a "real" size
* @return the size of the label.
*/
public static Dimension getLabelDimension(Node node, Dimension defaultDimension) {
Dimension labelSize = defaultDimension;
ViewQuery viewQuery = new ViewQuery(node);
EObject element = node.getElement();
if (element instanceof DDiagramElement) {
DDiagramElement dDiagramElement = (DDiagramElement) element;
org.eclipse.sirius.viewpoint.Style siriusStyle = dDiagramElement.getStyle();
if (!new DDiagramElementQuery(dDiagramElement).isLabelHidden()) {
if (siriusStyle instanceof BasicLabelStyle) {
BasicLabelStyle bls = (BasicLabelStyle) siriusStyle;
Font defaultFont = VisualBindingManager.getDefault().getFontFromLabelStyle(bls, (String) viewQuery.getDefaultValue(NotationPackage.Literals.FONT_STYLE__FONT_NAME));
try {
labelSize = FigureUtilities.getStringExtents(dDiagramElement.getName(), defaultFont);
if (bls.isShowIcon()) {
// Also consider the icon size
Dimension iconDimension = getIconDimension(dDiagramElement, bls);
labelSize.setHeight(Math.max(labelSize.height(), iconDimension.height));
labelSize.setWidth(labelSize.width() + ICON_TEXT_GAP + iconDimension.width);
}
} catch (SWTException e) {
// Probably an "Invalid thread access" (FigureUtilities
// creates a new Shell to compute the label size). So in
// this case, we use the default size.
}
}
}
}
return labelSize;
}
private static Dimension getIconDimension(DSemanticDecorator dSemanticDecorator, BasicLabelStyle bls) {
ImageDescriptor descriptor = null;
EObject target = dSemanticDecorator.getTarget();
if (!StringUtil.isEmpty(bls.getIconPath())) {
String iconPath = bls.getIconPath();
final File imageFile = FileProvider.getDefault().getFile(new Path(iconPath));
if (imageFile != null && imageFile.exists() && imageFile.canRead()) {
try {
descriptor = DiagramUIPlugin.Implementation.findImageDescriptor(imageFile.toURI().toURL());
} catch (MalformedURLException e) {
// Do nothing here
}
}
} else if (target != null) {
final IItemLabelProvider labelProvider = (IItemLabelProvider) DiagramUIPlugin.getPlugin().getItemProvidersAdapterFactory().adapt(target, IItemLabelProvider.class);
if (labelProvider != null) {
descriptor = ExtendedImageRegistry.getInstance().getImageDescriptor(labelProvider.getImage(target));
}
}
if (descriptor == null) {
descriptor = ImageDescriptor.getMissingImageDescriptor();
}
Image icon = DiagramUIPlugin.getPlugin().getImage(descriptor);
return new Dimension(icon.getBounds().width, icon.getBounds().height);
}
}