diff options
| author | Florian Barbin | 2014-11-18 09:18:39 +0000 |
|---|---|---|
| committer | Florian Barbin | 2014-12-01 16:05:12 +0000 |
| commit | deae56838017dae692745c2579845ad934832575 (patch) | |
| tree | dd45775d33200d9a4f03d3e3f07d6d27a997dd5f | |
| parent | 7f1e40da9ac5f691b33104fe1aafe7b952a197c8 (diff) | |
| download | org.eclipse.sirius-deae56838017dae692745c2579845ad934832575.tar.gz org.eclipse.sirius-deae56838017dae692745c2579845ad934832575.tar.xz org.eclipse.sirius-deae56838017dae692745c2579845ad934832575.zip | |
[448739] Improve the centering for straight and 2 segments edges.
* Some cases with straight rectilinear edges or two segments rectilinear
edges were not correctly handled if the two edge ends were centered. see
Comment 12 [1]
* Externalize the centered algorithm. (to use with the rectilinear
router)
[1] https://bugs.eclipse.org/bugs/show_bug.cgi?id=448739#c12
Bug: 448739
Change-Id: I7e8ff468893d6e767b23313db2af84d2fcf71c90
Signed-off-by: Florian Barbin <florian.barbin@obeo.fr>
2 files changed, 312 insertions, 83 deletions
diff --git a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/operation/CenterEdgeEndModelChangeOperation.java b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/operation/CenterEdgeEndModelChangeOperation.java index 8dc42e9c95..ae0301169b 100644 --- a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/operation/CenterEdgeEndModelChangeOperation.java +++ b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/operation/CenterEdgeEndModelChangeOperation.java @@ -18,7 +18,6 @@ import java.util.List; import org.eclipse.draw2d.Connection; import org.eclipse.draw2d.ConnectionLayer; import org.eclipse.draw2d.ConnectionRouter; -import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; @@ -54,6 +53,7 @@ import org.eclipse.sirius.diagram.ui.business.internal.operation.AbstractModelCh import org.eclipse.sirius.diagram.ui.graphical.figures.CanonicalRectilinearRouter; import org.eclipse.sirius.diagram.ui.internal.edit.parts.DEdgeEditPart; import org.eclipse.sirius.diagram.ui.internal.refresh.GMFHelper; +import org.eclipse.sirius.diagram.ui.tools.internal.routers.RectilinearEdgeUtil; import org.eclipse.sirius.diagram.ui.tools.internal.util.GMFNotationUtilities; import org.eclipse.sirius.ext.base.Option; import org.eclipse.sirius.ext.base.Options; @@ -69,6 +69,7 @@ import org.eclipse.ui.IEditorPart; * @author Florian Barbin * */ +@SuppressWarnings("restriction") public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperation<Void> { private Edge edge; @@ -91,6 +92,8 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat private Dimension targetFigureSize; + private static final String CENTERED_ANCHOR = GMFNotationUtilities.getTerminalString(0.5d, 0.5d); + /** * Constructor to use the connectionEditPart to compute the new edge * bendpoints. @@ -347,8 +350,6 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat */ private void handleRectilinearCase(CenteringStyle center, Rectangle sourceBounds, Rectangle targetBounds, PointList existingPointList) { - int sourceAnchorOrientation = PositionConstants.NONE; - int targetAnchorOrientation = PositionConstants.NONE; PointList rectilinear = null; // If the connection is available (the edge already exist) we retrieve @@ -356,9 +357,6 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat if (connection != null) { rectilinear = getRectilinearPointListFromConnection(); - sourceAnchorOrientation = computeSourceOrientation(rectilinear); - targetAnchorOrientation = computeTargetOrientation(rectilinear); - } else { rectilinear = existingPointList.getCopy(); @@ -373,16 +371,15 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat CanonicalRectilinearRouter rectilinearRouter = new CanonicalRectilinearRouter(); rectilinearRouter.routeLine(edge, 0, rectilinear, sourceBounds, targetBounds); - - sourceAnchorOrientation = computeSourceOrientation(rectilinear); - targetAnchorOrientation = computeTargetOrientation(rectilinear); } if (rectilinear.size() >= 2) { + RectilinearEdgeUtil.centerEdgeEnds(rectilinear, newSourceAnchorAbsoluteLocation, newTargetAnchorAbsoluteLocation, center); + if (center == CenteringStyle.BOTH || center == CenteringStyle.SOURCE) { - handleSourceRectilinearRoutingStyle(sourceBounds, rectilinear, sourceAnchorOrientation); + centerSourceAnchor(); } if (center == CenteringStyle.BOTH || center == CenteringStyle.TARGET) { - handleTargetRectilinearRoutingStyle(targetBounds, rectilinear, targetAnchorOrientation); + centerTargetAnchor(); } existingPointList.removeAllPoints(); @@ -390,20 +387,6 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat } } - private int computeSourceOrientation(PointList rectilinear) { - if (rectilinear.size() >= 2) { - return (rectilinear.getPoint(0).x() == rectilinear.getPoint(1).x()) ? PositionConstants.VERTICAL : PositionConstants.HORIZONTAL; - } - return PositionConstants.NONE; - } - - private int computeTargetOrientation(PointList rectilinear) { - if (rectilinear.size() >= 2) { - return (rectilinear.getPoint(rectilinear.size() - 1).x() == rectilinear.getPoint(rectilinear.size() - 2).x()) ? PositionConstants.VERTICAL : PositionConstants.HORIZONTAL; - } - return PositionConstants.NONE; - } - private void computePointListByIntersections(PointList rectilinear, Rectangle sourceBounds, Rectangle targetBounds) { Option<Point> sourceConnectionPoint = GraphicalHelper.getIntersection(existingSourceAnchorAbsoluteLocation, existingTargetAnchorAbsoluteLocation, sourceBounds, false); Option<Point> targetConnectionPoint = GraphicalHelper.getIntersection(existingSourceAnchorAbsoluteLocation, existingTargetAnchorAbsoluteLocation, targetBounds, false); @@ -414,57 +397,6 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat } } - private void handleSourceRectilinearRoutingStyle(Rectangle figureBounds, PointList rectilinear, int sourceAnchorRelativeLocation) { - - Point newConnectionPoint = rectilinear.getFirstPoint().getCopy(); - Point secondFromSrc = rectilinear.getPoint(1); - switch (sourceAnchorRelativeLocation) { - case PositionConstants.HORIZONTAL: - newConnectionPoint.setY(newSourceAnchorAbsoluteLocation.y()); - secondFromSrc.setY(newSourceAnchorAbsoluteLocation.y()); - break; - - case PositionConstants.VERTICAL: - newConnectionPoint.setX(newSourceAnchorAbsoluteLocation.x()); - secondFromSrc.setX(newSourceAnchorAbsoluteLocation.x()); - break; - - default: - // this case should not happen. - break; - } - - rectilinear.setPoint(newConnectionPoint, 0); - rectilinear.setPoint(secondFromSrc, 1); - centerSourceAnchor(); - - } - - private void handleTargetRectilinearRoutingStyle(Rectangle figureBounds, PointList rectilinear, int targetAnchorRelativeLocation) { - - Point newConnectionPoint = rectilinear.getLastPoint().getCopy(); - Point secondFromTgt = rectilinear.getPoint(rectilinear.size() - 2); - switch (targetAnchorRelativeLocation) { - case PositionConstants.HORIZONTAL: - newConnectionPoint.setY(newTargetAnchorAbsoluteLocation.y()); - secondFromTgt.setY(newTargetAnchorAbsoluteLocation.y()); - break; - - case PositionConstants.VERTICAL: - newConnectionPoint.setX(newTargetAnchorAbsoluteLocation.x()); - secondFromTgt.setX(newTargetAnchorAbsoluteLocation.x()); - break; - - default: - // this case should not happen. - break; - } - - rectilinear.setPoint(newConnectionPoint, rectilinear.size() - 1); - rectilinear.setPoint(secondFromTgt, rectilinear.size() - 2); - centerTargetAnchor(); - } - private void computeAndSetNewAnchorsAbsoluteCoordinates(Rectangle sourceBounds, Rectangle targetBounds, CenteringStyle center) { newSourceAnchorAbsoluteLocation = existingSourceAnchorAbsoluteLocation; @@ -501,6 +433,7 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat if (option.some()) { pointList = option.get(); } else { + @SuppressWarnings("unchecked") List<RelativeBendpoint> relativeBendpoints = bendpoints.getPoints(); for (int i = 0; i < relativeBendpoints.size(); i++) { float weight = i / ((float) relativeBendpoints.size() - 1); @@ -531,18 +464,36 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat * Set the source anchor at (0.5, 0.5). */ private void centerSourceAnchor() { - IdentityAnchor a = NotationFactory.eINSTANCE.createIdentityAnchor(); - a.setId(GMFNotationUtilities.getTerminalString(0.5d, 0.5d)); - edge.setSourceAnchor(a); + Anchor currentAnchor = edge.getSourceAnchor(); + IdentityAnchor newAnchor = setOrCreateNewAnchorIfNeeded(currentAnchor); + if (newAnchor != currentAnchor) { + edge.setSourceAnchor(newAnchor); + } + } /** * Set the target anchor at (0.5, 0.5). */ private void centerTargetAnchor() { - IdentityAnchor a = NotationFactory.eINSTANCE.createIdentityAnchor(); - a.setId(GMFNotationUtilities.getTerminalString(0.5d, 0.5d)); - edge.setTargetAnchor(a); + Anchor currentAnchor = edge.getTargetAnchor(); + IdentityAnchor newAnchor = setOrCreateNewAnchorIfNeeded(currentAnchor); + if (newAnchor != currentAnchor) { + edge.setTargetAnchor(newAnchor); + } + } + + private IdentityAnchor setOrCreateNewAnchorIfNeeded(Anchor anchor) { + if (anchor instanceof IdentityAnchor) { + if (!((IdentityAnchor) anchor).getId().equals(CENTERED_ANCHOR)) { + ((IdentityAnchor) anchor).setId(CENTERED_ANCHOR); + } + return (IdentityAnchor) anchor; + } else { + IdentityAnchor a = NotationFactory.eINSTANCE.createIdentityAnchor(); + a.setId(CENTERED_ANCHOR); + return a; + } } /** @@ -608,6 +559,7 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat /** * We try to retrieve the edge connection figure. */ + @SuppressWarnings("rawtypes") private void setConnectionIfNull() { if (connection != null || !useFigure) { return; @@ -653,7 +605,6 @@ public class CenterEdgeEndModelChangeOperation extends AbstractModelChangeOperat * * @return a rectilinear PointList. */ - @SuppressWarnings("restriction") private PointList getRectilinearPointListFromConnection() { ConnectionRouter oldConnectionRouter = connection.getConnectionRouter(); boolean needToRetrieveOldRouter = false; diff --git a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/tools/internal/routers/RectilinearEdgeUtil.java b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/tools/internal/routers/RectilinearEdgeUtil.java new file mode 100644 index 0000000000..d301259427 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/tools/internal/routers/RectilinearEdgeUtil.java @@ -0,0 +1,278 @@ +/******************************************************************************* + * Copyright (c) 2014 THALES GLOBAL SERVICES and others. + * 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 + * IBM Corporation - removeRedundantPoints(PointList) method + *******************************************************************************/ + +package org.eclipse.sirius.diagram.ui.tools.internal.routers; + +import org.eclipse.draw2d.PositionConstants; +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.draw2d.geometry.PointList; +import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg; +import org.eclipse.sirius.diagram.description.CenteringStyle; + +/** + * Utility class for rectilinear edges. Handles edge centering. + * + * @author Florian Barbin + * + */ +public final class RectilinearEdgeUtil { + + /** + * The minimal distance between an edge connection and the first bendpoint + * to add (if we need to add bendpoints). + */ + private static final int MINIMAL_SEGMENT_SIZE = 20; + + private RectilinearEdgeUtil() { + // private constructor. + } + + /** + * Centers the edge ends of the given rectilinear point list. + * + * @param line + * the point list (in absolute coordinates). We assume this list + * is rectilinear. + * @param srcRefPoint + * the source anchor absolute location. Must be centered if the + * centering style is BOTH or SOURCE. + * @param tgtRefPoint + * the target anchor absolute location. Must be centered if the + * centering style is BOTH or TARGET. + * @param centeringStyle + * the centering style. + */ + public static void centerEdgeEnds(PointList line, Point srcRefPoint, Point tgtRefPoint, CenteringStyle centeringStyle) { + if (CenteringStyle.BOTH == centeringStyle || CenteringStyle.SOURCE == centeringStyle) { + + addPointsIfNeeded(line, srcRefPoint, true); + alignSegmentTowardAnchor(line, srcRefPoint, true); + } + if (CenteringStyle.BOTH == centeringStyle || CenteringStyle.TARGET == centeringStyle) { + + addPointsIfNeeded(line, tgtRefPoint, false); + alignSegmentTowardAnchor(line, tgtRefPoint, false); + } + removeRedundantPoints(line); + } + + /** + * Adds transitional bendpoints in the cases where centering one segment + * changes the other segment orientation. + * + * @param line + * the rectilinear edge point list. + * @param absoluteAnchorCoordinates + * the anchor absolute coordinates where the segment should be + * aligned + * @param isFirst + * true if it concerns the source or false for the target. + */ + private static void addPointsIfNeeded(PointList line, Point absoluteAnchorCoordinates, boolean isFirst) { + if (line.size() == 2) { + addPointForStraightEdge(line); + } else if (line.size() == 3 && isOppositeEndWillBeAffected(line, absoluteAnchorCoordinates, isFirst)) { + addPointForLEdge(line, isFirst); + } + } + + /** + * For straight edges, we need to had 2 bendpoints at the middle of the line + * to center only one segment at time. + * + * @param line + * the current 2 points list. This point list will be modified by + * adding two points in the middle. + */ + private static void addPointForStraightEdge(PointList line) { + Point pointToInsert = null; + int segmentOrientation = line.getFirstPoint().x == line.getLastPoint().x ? PositionConstants.VERTICAL : PositionConstants.HORIZONTAL; + if (segmentOrientation == PositionConstants.VERTICAL) { + int xValue = line.getFirstPoint().x; + int delta = (line.getFirstPoint().y - line.getLastPoint().y) / 2; + pointToInsert = new Point(xValue, line.getFirstPoint().y - delta); + } else { + int yValue = line.getFirstPoint().y; + int delta = (line.getFirstPoint().x - line.getLastPoint().x) / 2; + pointToInsert = new Point(line.getFirstPoint().x - delta, yValue); + } + line.insertPoint(pointToInsert, 1); + line.insertPoint(pointToInsert.getCopy(), 1); + + } + + /** + * In the case of an edge with 3 bendpoints (the edge forms a "L"), shifting + * one segment could change the other segment connection point. + * + * @param newLine + * the edge point list. + * @param absoluteAnchorCoordinates + * the coordinate of the anchor we want to align the segment. + * @param isFirst + * true if want to move the source segment. Otherwise false. + * @return True if the perpendicular segment will change of shape face. + */ + private static boolean isOppositeEndWillBeAffected(PointList line, Point absoluteAnchorCoordinates, boolean isFirst) { + LineSeg oppositeSegment = getSegment(line, !isFirst); + int origTermDelta = 0; + int origNewLocDelta = 0; + + if (oppositeSegment.isVertical()) { + origTermDelta = oppositeSegment.getOrigin().y - oppositeSegment.getTerminus().y; + origNewLocDelta = oppositeSegment.getOrigin().y - absoluteAnchorCoordinates.y; + } else { + origTermDelta = oppositeSegment.getOrigin().x - oppositeSegment.getTerminus().x; + origNewLocDelta = oppositeSegment.getOrigin().x - absoluteAnchorCoordinates.x; + + } + + return (origTermDelta > 0) != (origNewLocDelta > 0); + + } + + /** + * Adds two bendpoints on the segment we want to align with the anchor. That + * avoids to break the other segment when centering this one. (in case of + * edge with 3 bendpoints that means two perpendicular segments). + * + * @param line + * the point list to add new bendpoints. + * @param isFirst + * true if the first segment is concerned (from the source), + * false for the second one (the last from the target). + */ + private static void addPointForLEdge(PointList line, boolean isFirst) { + LineSeg segment = getSegment(line, isFirst); + Point origin = segment.getOrigin(); + Point terminus = segment.getTerminus(); + Point newPoint = origin.getCopy(); + if (segment.isHorizontal()) { + int newPointX; + if (origin.x > terminus.x) { + newPointX = origin.x - MINIMAL_SEGMENT_SIZE; + } else { + newPointX = origin.x + MINIMAL_SEGMENT_SIZE; + } + newPoint.setX(newPointX); + + } else { + + int newPointY; + if (origin.y > terminus.y) { + newPointY = origin.y - MINIMAL_SEGMENT_SIZE; + } else { + newPointY = origin.y + MINIMAL_SEGMENT_SIZE; + } + newPoint.setY(newPointY); + } + if (isFirst) { + line.insertPoint(newPoint, 1); + line.insertPoint(newPoint.getCopy(), 1); + } else { + + line.insertPoint(newPoint, line.size() - 1); + line.insertPoint(newPoint.getCopy(), line.size() - 1); + } + + } + + /** + * Aligns the given segment toward anchor. + * + * @param line + * the edge point list. + * @param absoluteAnchorCoordinates + * the anchor coordinates. + * @param isFirst + * true to align the first segment (from the source), false to + * align the last one (from the target). + */ + private static void alignSegmentTowardAnchor(PointList line, Point absoluteAnchorCoordinates, boolean isFirst) { + LineSeg segment = getSegment(line, isFirst); + Point origin = segment.getOrigin(); + Point terminus = segment.getTerminus(); + if (segment.isVertical()) { + origin.setX(absoluteAnchorCoordinates.x()); + terminus.setX(absoluteAnchorCoordinates.x()); + } else { + origin.setY(absoluteAnchorCoordinates.y()); + terminus.setY(absoluteAnchorCoordinates.y()); + } + if (isFirst) { + line.setPoint(origin, 0); + line.setPoint(terminus, 1); + } else { + line.setPoint(origin, line.size() - 1); + line.setPoint(terminus, line.size() - 2); + } + } + + /** + * Gives the first or the last edge segment from the line. + * + * @param line + * the edge point list. + * @param isFirst + * true to get the first segment, false to get the last one. + * @return the segment. The origin point is always the one connected to the + * shape. For the first segment, the origin is the first point, for + * the last segment, the origin is the last point. + */ + private static LineSeg getSegment(PointList line, boolean isFirst) { + if (isFirst) { + return new LineSeg(line.getFirstPoint(), line.getPoint(1)); + } else { + return new LineSeg(line.getLastPoint(), line.getPoint(line.size() - 2)); + } + } + + /** + * From + * org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter. + * removeRedundantPoints(PointList) Iterates through points of a polyline + * and does the following: if 3 points lie on the same line the middle point + * is removed + * + * @param line + * polyline's points + */ + private static boolean removeRedundantPoints(PointList line) { + int initialNumberOfPoints = line.size(); + if (line.size() > 2) { + PointList newLine = new PointList(line.size()); + newLine.addPoint(line.removePoint(0)); + while (line.size() >= 2) { + Point p0 = newLine.getLastPoint(); + Point p1 = line.getPoint(0); + Point p2 = line.getPoint(1); + if (p0.x == p1.x && p0.x == p2.x) { + // Have two vertical segments in a row + // get rid of the point between + line.removePoint(0); + } else if (p0.y == p1.y && p0.y == p2.y) { + // Have two horizontal segments in a row + // get rid of the point between + line.removePoint(0); + } else { + newLine.addPoint(line.removePoint(0)); + } + } + while (line.size() > 0) { + newLine.addPoint(line.removePoint(0)); + } + line.removeAllPoints(); + line.addAll(newLine); + } + return line.size() != initialNumberOfPoints; + } +} |
