blob: 5b1d0f4b3c092e5b5ada75a06300162c9037aee6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 THALES GLOBAL SERVICES.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.diagram.ui.business.internal.migration;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.editparts.LabelEditPart;
import org.eclipse.gmf.runtime.diagram.ui.internal.util.LabelViewConstants;
import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
import org.eclipse.gmf.runtime.notation.Anchor;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.IdentityAnchor;
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.datatype.RelativeBendpoint;
import org.eclipse.sirius.business.api.migration.AbstractRepresentationsFileMigrationParticipant;
import org.eclipse.sirius.diagram.DDiagram;
import org.eclipse.sirius.diagram.DiagramPlugin;
import org.eclipse.sirius.diagram.ui.business.api.query.DDiagramGraphicalQuery;
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.internal.edit.parts.DEdgeBeginNameEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.DEdgeEndNameEditPart;
import org.eclipse.sirius.diagram.ui.internal.edit.parts.DEdgeNameEditPart;
import org.eclipse.sirius.diagram.ui.part.SiriusVisualIDRegistry;
import org.eclipse.sirius.diagram.ui.provider.Messages;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.viewpoint.DAnalysis;
import org.eclipse.sirius.viewpoint.DView;
import org.osgi.framework.Version;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* A representation file migration to detect labels that are far from their edge
* and reset them to their default location.
*
* @author lredor
*/
@SuppressWarnings("restriction")
public class SnapBackDistantLabelsMigrationParticipant extends AbstractRepresentationsFileMigrationParticipant {
/**
* The Sirius version for which this migration is added.
*/
public static final Version MIGRATION_VERSION = new Version("10.1.9.201706271200"); //$NON-NLS-1$
/**
* The absolute maximum x or y coordinate from which label is considered
* distant.
*/
private static final int LABEL_UPPER_LIMIT = 1000;
/**
* The absolute minimal x or y coordinate from which label is not considered
* distant.
*/
private static final int LABEL_LOWER_LIMIT = 250;
/**
* The absolute maximum x or y coordinates from which node is considered
* with "very huge coordinates".
*/
private static final int NODES_LIMIT = 1000000;
/**
* Nodes with "very huge coordinates" are relocated to location near from
* it. This value is arbitrary. It allows to "easily" see elements with a
* zoom of 10%.
*/
private static final int NODES_LIMIT_MOVE = 5000;
private static final String NEGATIVE_X_KEY = "-x"; //$NON-NLS-1$
private static final String POSITIVE_X_KEY = "+x"; //$NON-NLS-1$
private static final String NEGATIVE_Y_KEY = "-y"; //$NON-NLS-1$
private static final String POSITIVE_Y_KEY = "+y"; //$NON-NLS-1$
/**
* Determines a setter based on an input value and a new value.
*
* @author lredor
*
*/
public interface Setter<F, T> {
/**
* Set a value of type <code>F</code> with the new value <code>T</code>.
*
* @param input
* The object to modify
* @param newValue
* The new value to set one property of <code>input</code>
*/
void set(F input, T newValue);
}
@Override
public Version getMigrationVersion() {
return MIGRATION_VERSION;
}
@Override
protected void postLoad(DAnalysis dAnalysis, Version loadedVersion) {
if (loadedVersion.compareTo(MIGRATION_VERSION) < 0) {
initializeSnapBackPositionMap();
boolean isModified = false;
StringBuilder sb = new StringBuilder(Messages.SnapBackDistantLabelsMigrationParticipant_title);
List<Node> distantEdgeLabels = new ArrayList<Node>();
for (DView dView : dAnalysis.getOwnedViews()) {
for (DDiagram dDiagram : Iterables.filter(dView.getOwnedRepresentations(), DDiagram.class)) {
// Search distant edge labels
List<Node> currentDistantEdgeLabels = getDistantEdgeLabels(dDiagram);
distantEdgeLabels.addAll(currentDistantEdgeLabels);
if (currentDistantEdgeLabels.size() > 0) {
isModified = true;
// Add information to be logged
if (currentDistantEdgeLabels.size() > 1) {
sb.append(MessageFormat.format(Messages.SnapBackDistantLabelsMigrationParticipant_severalLabelsSnapBacked, currentDistantEdgeLabels.size(), dDiagram.getName()));
} else {
sb.append(MessageFormat.format(Messages.SnapBackDistantLabelsMigrationParticipant_oneLabelSnapBacked, dDiagram.getName()));
}
// Search and fix nodes with huge coordinates.
boolean nodesMoved = false;
Map<String, Map<Node, Integer>> currentnodesWithExtremeLocation = getNodesWithExtremeLocation(dDiagram);
for (String key : currentnodesWithExtremeLocation.keySet()) {
if (NEGATIVE_X_KEY.equals(key)) {
nodesMoved = moveNodesX(currentnodesWithExtremeLocation.get(key), false);
} else if (POSITIVE_X_KEY.equals(key)) {
nodesMoved = moveNodesX(currentnodesWithExtremeLocation.get(key), true);
} else if (NEGATIVE_Y_KEY.equals(key)) {
nodesMoved = moveNodesY(currentnodesWithExtremeLocation.get(key), false);
} else {
nodesMoved = moveNodesY(currentnodesWithExtremeLocation.get(key), true);
}
}
if (nodesMoved) {
// Add information to be logged
sb.append(Messages.SnapBackDistantLabelsMigrationParticipant_nodesMoved);
}
}
}
}
// Fix distant edge labels
for (Node distantEdgeLabel : distantEdgeLabels) {
Point offset = LabelEditPart.getSnapBackPosition(distantEdgeLabel.getType());
if (offset != null) {
ViewUtil.setStructuralFeatureValue(distantEdgeLabel, NotationPackage.eINSTANCE.getLocation_X(), Integer.valueOf(offset.x));
ViewUtil.setStructuralFeatureValue(distantEdgeLabel, NotationPackage.eINSTANCE.getLocation_Y(), Integer.valueOf(offset.y));
}
}
if (isModified) {
DiagramPlugin.getDefault().logInfo(sb.toString());
}
}
}
/**
* Move all nodes to a more readable location.
*
* @param nodesWithExtremeLocation
* List of couple, node with extreme location and corresponding
* huge coordinate.
* @param isPositive
* true if the above list of nodes concerns nodes with positive
* X, false if it concerns negative X
* @return true if at least one node has been moved, false otherwise
*/
protected boolean moveNodesX(Map<Node, Integer> nodesWithExtremeLocation, boolean isPositive) {
return moveNodes(nodesWithExtremeLocation, isPositive, new Setter<Location, Integer>() {
@Override
public void set(Location input, Integer newValue) {
input.setX(newValue);
}
});
}
/**
* Move all nodes to a more readable location.
*
* @param nodesWithExtremeLocation
* List of couple, node with extreme location and corresponding
* huge coordinate.
* @param isPositive
* true if the above list of nodes concerns nodes with positive
* X, false if it concerns negative X
* @return true if at least one node has been moved, false otherwise
*/
protected boolean moveNodesY(Map<Node, Integer> nodesWithExtremeLocation, boolean isPositive) {
return moveNodes(nodesWithExtremeLocation, isPositive, new Setter<Location, Integer>() {
@Override
public void set(Location input, Integer newValue) {
input.setY(newValue);
}
});
}
/**
* Move all nodes to a more readable location.
*
* @param nodesWithExtremeLocation
* List of couple, node with extreme location and corresponding
* location.
* @param isPositive
* true if the above list of nodes concerns nodes with positive X
* or Y, false if it concerns negative X or Y
* @param setter
* A setter to set new X or Y coordinate
* @return true if at least one node has been moved, false otherwise
*/
protected boolean moveNodes(Map<Node, Integer> nodesWithExtremeLocation, boolean isPositive, Setter<Location, Integer> setter) {
boolean nodesMoved = false;
int min = -NODES_LIMIT;
int max = Integer.MIN_VALUE;
if (isPositive) {
min = Integer.MAX_VALUE;
max = NODES_LIMIT;
}
// Get min and max values
for (Integer coordinate : nodesWithExtremeLocation.values()) {
nodesMoved = true;
if (coordinate < min) {
min = coordinate;
}
if (coordinate > max) {
max = coordinate;
}
}
// Get delta between min and max
int delta = max - min;
if (delta < NODES_LIMIT) {
// Moves all nodes with the same delta
for (Entry<Node, Integer> entry : nodesWithExtremeLocation.entrySet()) {
if (isPositive) {
setter.set((Location) entry.getKey().getLayoutConstraint(), entry.getValue() - max - NODES_LIMIT_MOVE);
} else {
setter.set((Location) entry.getKey().getLayoutConstraint(), entry.getValue() - min + NODES_LIMIT_MOVE);
}
}
} else {
// Move all nodes to the same location (-NODES_LIMIT)
for (Entry<Node, Integer> entry : nodesWithExtremeLocation.entrySet()) {
if (isPositive) {
setter.set((Location) entry.getKey().getLayoutConstraint(), -NODES_LIMIT_MOVE);
} else {
setter.set((Location) entry.getKey().getLayoutConstraint(), NODES_LIMIT_MOVE);
}
}
}
return nodesMoved;
}
/**
* Create dummy edge name edit part to initialize the
* registerSnapBackPosition() method of each kind of edit part. Without this
* initialization, when using
* {@link LabelEditPart#getSnapBackPosition(String)}, the returned value is
* null.
*/
private void initializeSnapBackPositionMap() {
new DEdgeNameEditPart(null);
new DEdgeBeginNameEditPart(null);
new DEdgeEndNameEditPart(null);
}
private List<Node> getDistantEdgeLabels(DDiagram dDiagram) {
List<Node> distantEdgeLabels = new ArrayList<Node>();
for (Edge edge : getEdges(dDiagram)) {
for (Object child : edge.getChildren()) {
if (child instanceof Node && new ViewQuery((Node) child).isForEdgeNameEditPart()) {
if (isLabelDistant(edge, (Node) child)) {
distantEdgeLabels.add((Node) child);
}
}
}
}
return distantEdgeLabels;
}
/**
* Get all nodes with huge coordinates for this <code>dDiagram</code>. The
* nodes are grouped by nodes with negative X, positive X, negative Y and
* positive Y (using key {@link #NEGATIVE_X_KEY}, {@link #POSITIVE_X_KEY},
* {@link #NEGATIVE_Y_KEY}, {@link #POSITIVE_Y_KEY}).
*
* @param dDiagram
* The diagram in which to search.
* @return a list of nodes with the "huge coordinate" group by type.
*/
private Map<String, Map<Node, Integer>> getNodesWithExtremeLocation(DDiagram dDiagram) {
Map<Node, Integer> nodesWithExtremeNegativeXLocation = new HashMap<Node, Integer>();
Map<Node, Integer> nodesWithExtremePositiveXLocation = new HashMap<Node, Integer>();
Map<Node, Integer> nodesWithExtremeNegativeYLocation = new HashMap<Node, Integer>();
Map<Node, Integer> nodesWithExtremePositiveYLocation = new HashMap<Node, Integer>();
for (Node node : getNodes(dDiagram)) {
if (node.getLayoutConstraint() instanceof Location) {
Location location = (Location) node.getLayoutConstraint();
Point point = new Point(location.getX(), location.getY());
// Do not deal with label between -lowerLimit and lowerLimit.
if (Math.abs(location.getX()) > NODES_LIMIT) {
if (location.getX() > 0) {
nodesWithExtremePositiveXLocation.put(node, point.x());
} else {
nodesWithExtremeNegativeXLocation.put(node, point.x());
}
}
if (Math.abs(location.getY()) > NODES_LIMIT) {
if (location.getY() > 0) {
nodesWithExtremePositiveYLocation.put(node, point.y());
} else {
nodesWithExtremeNegativeYLocation.put(node, point.y());
}
}
}
}
Map<String, Map<Node, Integer>> result = new HashMap<String, Map<Node, Integer>>();
result.put(NEGATIVE_X_KEY, nodesWithExtremeNegativeXLocation);
result.put(POSITIVE_X_KEY, nodesWithExtremePositiveXLocation);
result.put(NEGATIVE_Y_KEY, nodesWithExtremeNegativeYLocation);
result.put(POSITIVE_Y_KEY, nodesWithExtremePositiveYLocation);
return result;
}
/**
* Check is the label is considered as distant from its edge:
* <UL>
* <LI>If the label is less than 250 pixels away from the reference point on
* its corresponding edge, the label is not considered as distant.</LI>
* <LI>If the label is more than 1000 pixels away from the reference point
* on its corresponding edge, the label is considered as distant.</LI>
* <LI>Between these 2 limits, the label is considered as distant if the
* distance between the center of label and edge is highest that
* "4*the size of the nearest segment".</LI>
* </UL>
*
* @param edgeLabel
* The label to check
* @return true if the label is considered as distant from its edge, false
* otherwise.
*/
private boolean isLabelDistant(Edge edge, Node edgeLabel) {
boolean isLabelDistant = false;
if (edgeLabel.getLayoutConstraint() instanceof Location) {
Location location = (Location) edgeLabel.getLayoutConstraint();
// Do not deal with label between -lowerLimit and lowerLimit.
if (!(Math.abs(location.getX()) <= LABEL_LOWER_LIMIT && Math.abs(location.getY()) <= LABEL_LOWER_LIMIT)) {
isLabelDistant = Math.abs(location.getX()) > LABEL_UPPER_LIMIT || Math.abs(location.getY()) > LABEL_UPPER_LIMIT;
if (!isLabelDistant) {
try {
// Try to be more precise that just using location of
// label: Check if the distance between center of label
// and edge is highest that "4*the size of the nearest
// segment".
List<Point> result = Lists.newArrayList();
PointList ptList = new PointList();
Anchor anchor = edge.getSourceAnchor();
// We only handle case with node as source and identity
// anchor.
if (anchor instanceof IdentityAnchor && edge.getSource() instanceof Node) {
PrecisionPoint relativeReference = BaseSlidableAnchor.parseTerminalString(((IdentityAnchor) anchor).getId());
Rectangle sourceBounds = new NodeQuery((Node) edge.getSource()).getHandleBounds();
PrecisionPoint srcAnchorLoc = new PrecisionPoint(relativeReference.preciseX() * sourceBounds.width() + sourceBounds.x(),
relativeReference.preciseY() * sourceBounds.height() + sourceBounds.y());
RelativeBendpoints bp = (RelativeBendpoints) edge.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);
ptList.addPoint(fromSrc);
}
Point refPoint = PointListUtilities.calculatePointRelativeToLine(ptList, 0, getLocationOfReference(edgeLabel), true);
Point labelCenter = org.eclipse.sirius.diagram.ui.internal.edit.parts.locator.EdgeLabelQuery.relativeCenterCoordinateFromOffset(ptList, refPoint,
new Point(location.getX(), location.getY()));
@SuppressWarnings("rawtypes")
List segments = PointListUtilities.getLineSegments(ptList);
LineSeg nearestSegment = PointListUtilities.getNearestSegment(segments, labelCenter.x, labelCenter.y);
double distance = nearestSegment.distanceToPoint(labelCenter.x(), labelCenter.y());
isLabelDistant = distance > nearestSegment.length() * 4;
}
// CHECKSTYLE:OFF
} catch (Exception e) {
// Do nothing, just avoid some specific cases not
// handled above.
}
// CHECKSTYLE:ON
}
}
}
return isLabelDistant;
}
@SuppressWarnings("restriction")
private int getLocationOfReference(Node edgeLabel) {
int locationOfRef = LabelViewConstants.MIDDLE_LOCATION;
int type = SiriusVisualIDRegistry.getVisualID(edgeLabel.getType());
if (DEdgeBeginNameEditPart.VISUAL_ID == type) {
locationOfRef = LabelViewConstants.SOURCE_LOCATION;
} else if (DEdgeEndNameEditPart.VISUAL_ID == type) {
locationOfRef = LabelViewConstants.TARGET_LOCATION;
}
return locationOfRef;
}
/**
* Get the {@link Edge}s with labels contained in the given {@link DDiagram}
* .
*
* @param dDiagram
* the given {@link DDiagram}.
* @return the {@link Edge}s with labels contained in the given
* {@link DDiagram}.
*/
private Collection<Edge> getEdges(DDiagram dDiagram) {
DDiagramGraphicalQuery query = new DDiagramGraphicalQuery(dDiagram);
Option<Diagram> gmfDiagram = query.getAssociatedGMFDiagram();
if (gmfDiagram.some()) {
return Lists.newArrayList(Iterables.filter(gmfDiagram.get().getEdges(), Edge.class));
}
return new ArrayList<Edge>();
}
/**
* Get the {@link Edge}s with labels contained in the given {@link DDiagram}
* .
*
* @param dDiagram
* the given {@link DDiagram}.
* @return the {@link Edge}s with labels contained in the given
* {@link DDiagram}.
*/
private Collection<Node> getNodes(DDiagram dDiagram) {
DDiagramGraphicalQuery query = new DDiagramGraphicalQuery(dDiagram);
Option<Diagram> gmfDiagram = query.getAssociatedGMFDiagram();
if (gmfDiagram.some()) {
return Lists.newArrayList(Iterables.filter(gmfDiagram.get().getChildren(), Node.class));
}
return new ArrayList<Node>();
}
}