blob: 8541fa5221df873f29542eb84c313e0283d40711 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2021 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.ext.gmf.runtime.editparts;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.FreeformViewport;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.handles.HandleBounds;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramRootEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
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.IdentityAnchor;
import org.eclipse.sirius.ext.draw2d.figure.FigureUtilities;
import org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.IFigureWithoutLabels;
/**
* Utility class to collect helper methods which deal with GraphicalOrdering but which are not part of its API.
*
* @author nlepine
*/
public final class GraphicalHelper {
private GraphicalHelper() {
// Prevent instantiation.
}
/**
* Get the zoom factor.
*
* @param part
* the current part
* @return the zoom factor
*/
public static double getZoom(EditPart part) {
Objects.requireNonNull(part);
double scale = 1.0;
RootEditPart root = part.getRoot();
if (root instanceof DiagramRootEditPart) {
DiagramRootEditPart rootEditPart = (DiagramRootEditPart) root;
scale = rootEditPart.getZoomManager().getZoom();
}
return scale;
}
/**
* Applied zoom on relative point.<BR>
* For example:
* <UL>
* <LI>For a zoom of 200%, the result of this method for the point (100, 100) is (200, 200)</LI>
* <LI>For a zoom of 50%, the result of this method for the point (100, 100) is (50, 50)</LI>
* </UL>
*
* @param part
* the current part
* @param relativePoint
* relative point
* @deprecated Use {@link #applyInverseZoomOnPoint(IGraphicalEditPart, Point)} instead
*/
@Deprecated
public static void appliedZoomOnRelativePoint(IGraphicalEditPart part, Point relativePoint) {
double zoom = getZoom(part);
if (relativePoint instanceof PrecisionPoint) {
((PrecisionPoint) relativePoint).setPreciseLocation(relativePoint.preciseX() / zoom, relativePoint.preciseY() / zoom);
} else {
relativePoint.setLocation((int) (relativePoint.x * zoom), (int) (relativePoint.y * zoom));
}
}
/**
* Applies zoom on a point and returns this point for convenience.<BR>
* For example:
* <UL>
* <LI>For a zoom of 200%, the result of this method for the point (100, 100) is (200, 200)</LI>
* <LI>For a zoom of 50%, the result of this method for the point (100, 100) is (50, 50)</LI>
* </UL>
*
* @param part
* the current part
* @param point
* a point
* @return <code>point</code> for convenience
*/
public static Point applyZoomOnPoint(IGraphicalEditPart part, Point point) {
double zoom = getZoom(part);
if (point instanceof PrecisionPoint) {
((PrecisionPoint) point).setPreciseLocation(point.preciseX() * zoom, point.preciseY() * zoom);
} else {
point.setLocation((int) (point.x * zoom), (int) (point.y * zoom));
}
return point;
}
/**
* Applies inverse zoom on a point and returns this point for convenience. <BR>
* For example:
* <UL>
* <LI>For a zoom of 200%, the result of this method for the point (100, 100) is (50, 50)</LI>
* <LI>For a zoom of 50%, the result of this method for the point (100, 100) is (200, 200)</LI>
* </UL>
*
* @param part
* the current part
* @param point
* a point
* @return <code>point</code> for convenience
*/
public static Point applyInverseZoomOnPoint(IGraphicalEditPart part, Point point) {
double zoom = getZoom(part);
if (point instanceof PrecisionPoint) {
((PrecisionPoint) point).setPreciseLocation(point.preciseX() / zoom, point.preciseY() / zoom);
} else {
point.setLocation((int) (point.x / zoom), (int) (point.y / zoom));
}
return point;
}
/**
* Set the zoom factor.
*
* @param part
* the current part
* @param scale
* the zoom factor
*/
public static void setZoom(IGraphicalEditPart part, double scale) {
Objects.requireNonNull(part);
RootEditPart root = part.getRoot();
if (root instanceof DiagramRootEditPart) {
DiagramRootEditPart rootEditPart = (DiagramRootEditPart) root;
rootEditPart.getZoomManager().setZoom(scale);
}
}
/**
* Returns the difference between the logical origin (0, 0) and the top-left point actually visible. This
* corresponds to how much the scrollbars "shift" the diagram.
*
* @param part
* an edit part on the view
* @return the scroll size
*/
public static Point getScrollSize(GraphicalEditPart part) {
Objects.requireNonNull(part);
FreeformViewport viewport = FigureUtilities.getRootFreeformViewport(part.getFigure());
if (viewport != null) {
return viewport.getViewLocation();
} else {
return new Point(0, 0);
}
}
/**
* Returns the difference between the logical origin (0, 0) and the top-left point actually visible. This
* corresponds to how much the scrollbars "shift" the successive containers.
*
* @param part
* an edit part on the view
* @return the scroll size
*/
protected static Point getContainerScrollSize(GraphicalEditPart part) {
Objects.requireNonNull(part);
Point result = new Point(0, 0);
Point diagramScrollSize = new Point(0, 0);
IFigure current = part.getFigure();
FreeformViewport rootFreeformViewport = null;
while (current != null) {
if (current instanceof FreeformViewport) {
rootFreeformViewport = (FreeformViewport) current;
diagramScrollSize = rootFreeformViewport.getViewLocation();
result.translate(diagramScrollSize);
}
current = current.getParent();
}
result.translate(diagramScrollSize.negate());
return result;
}
/**
* .
*
* @param part
* an edit part on the view
* @param scrollPosition
* the scroll size
*/
public static void setScrollSize(IGraphicalEditPart part, Point scrollPosition) {
Objects.requireNonNull(part);
// FreeformViewport viewport =
// FigureUtilities.getFreeformViewport(part.getFigure());
// if (viewport != null) {
// viewport.setLocation(scrollPosition);
// }
if (part.getViewer().getControl() instanceof FigureCanvas) {
// UIThreadRunnable.syncExec(new VoidResult() {
// public void run() {
((FigureCanvas) part.getViewer().getControl()).scrollTo(scrollPosition.x, scrollPosition.y);
// }
// });
}
}
/**
* Converts a point from screen coordinates to logical coordinates.
*
* @param point
* the point to convert.
* @param part
* a part from the diagram.
*/
public static void screen2logical(Point point, GraphicalEditPart part) {
point.translate(GraphicalHelper.getScrollSize(part));
point.performScale(1.0d / GraphicalHelper.getZoom(part));
}
/**
* Converts a rectangle from screen coordinates to logical coordinates.
*
* @param rect
* the rectangle to convert.
* @param part
* a part from the diagram.
*/
public static void screen2logical(Rectangle rect, GraphicalEditPart part) {
screen2logical(rect, part, false);
}
/**
* Converts a rectangle from screen coordinates to logical coordinates.
*
* @param rect
* the rectangle to convert.
* @param part
* a part from the diagram.
* @param considerAllScroll
* In all cases, the scroll of the diagram is used, but the scroll of containers are considered only if
* this parameter is true.
*/
protected static void screen2logical(Rectangle rect, GraphicalEditPart part, boolean considerAllScroll) {
double zoom = GraphicalHelper.getZoom(part);
if (considerAllScroll) {
rect.translate(GraphicalHelper.getContainerScrollSize(part).scale(zoom));
}
rect.translate(GraphicalHelper.getScrollSize(part));
rect.performScale(1.0d / zoom);
}
/**
* Converts a dimension from screen coordinates to logical coordinates. Dimensions have no defined position, so only
* the current zoom level is take into account, not the scroll state.
*
* @param dim
* the dimension to convert.
* @param part
* a part from the diagram.
*/
public static void screen2logical(Dimension dim, IGraphicalEditPart part) {
dim.performScale(1.0d / GraphicalHelper.getZoom(part));
}
/**
* Converts a point from logical coordinates to screen coordinates.
*
* @param point
* the point to convert.
* @param part
* a part from the diagram.
*/
public static void logical2screen(Point point, IGraphicalEditPart part) {
point.performScale(GraphicalHelper.getZoom(part));
point.translate(GraphicalHelper.getScrollSize(part).negate());
}
/**
* Converts a rectangle from logical coordinates to screen coordinates.
*
* @param rect
* the rectangle to convert.
* @param part
* a part from the diagram.
*/
public static void logical2screen(Rectangle rect, IGraphicalEditPart part) {
rect.performScale(GraphicalHelper.getZoom(part));
rect.translate(GraphicalHelper.getScrollSize(part).negate());
}
/**
* Converts a dimension from logical coordinates to screen coordinates. Dimensions have no defined position, so only
* the current zoom level is take into account, not the scroll state.
*
* @param dim
* the dimension to convert.
* @param part
* a part from the diagram.
*/
public static void logical2Screen(Dimension dim, IGraphicalEditPart part) {
dim.performScale(GraphicalHelper.getZoom(part));
}
/**
* Return the Point (absolute draw2d coordinates) corresponding to this Anchor. If anchor is not an IdentityAnchor,
* the center of <code>parent</code> is returned.
*
* @param parent
* The parent node
* @param anchor
* The anchor
* @return The corresponding point to this anchor
*/
public static Point getAnchorPoint(GraphicalEditPart parent, Anchor anchor) {
if (anchor instanceof IdentityAnchor) {
return getAnchorPoint(parent, (IdentityAnchor) anchor);
} else {
return getAnchorPoint(parent, null);
}
}
/**
* Return the Point (absolute draw2d coordinates) corresponding to this Anchor. If anchor is null, the center of
* <code>parent</code> is returned.
*
* @param parent
* The parent node
* @param anchor
* The anchor
* @return The corresponding point to this anchor
*/
public static Point getAnchorPoint(GraphicalEditPart parent, IdentityAnchor anchor) {
PrecisionRectangle bounds;
if (parent.getFigure() instanceof HandleBounds) {
bounds = new PrecisionRectangle(((HandleBounds) parent.getFigure()).getHandleBounds());
} else {
bounds = new PrecisionRectangle(parent.getFigure().getBounds());
}
parent.getFigure().translateToAbsolute(bounds);
screen2logical(bounds, parent);
PrecisionPoint rel;
if (anchor != null) {
rel = BaseSlidableAnchor.parseTerminalString(anchor.getId());
} else {
// If anchor is null, the default value is (0.5, 0.5)
rel = new PrecisionPoint(0.5, 0.5);
}
Point location = new PrecisionPoint(bounds.preciseX() + bounds.preciseWidth() * rel.preciseX(), bounds.preciseY() + bounds.preciseHeight() * rel.preciseY());
return location;
}
/**
* Get intersection between a line between lineOrigin and lineTerminus, and the rectangle bounds of the part. If
* there are several intersections, the shortest is returned.
*
* @param lineOrigin
* Origin of the line
* @param lineTerminus
* Terminus of the line
* @param part
* Part to detect intersection.
* @param minimalDistancefromLineOrigin
* true if the shortest distance is between the line origin and the part, false otherwise.
* @return Intersection between a line and a rectangle.
*/
public static Optional<Point> getIntersection(Point lineOrigin, Point lineTerminus, IGraphicalEditPart part, boolean minimalDistancefromLineOrigin) {
return getIntersection(lineOrigin, lineTerminus, part, minimalDistancefromLineOrigin, false);
}
/**
* Get intersection between a line between lineOrigin and lineTerminus, and the rectangle bounds of the part. If
* there are several intersections, the shortest is returned.
*
* @param lineOrigin
* Origin of the line
* @param lineTerminus
* Terminus of the line
* @param part
* Part to detect intersection.
* @param minimalDistancefromLineOrigin
* true if the shortest distance is between the line origin and the part, false otherwise.
* @param useNearestPoint
* If true, if there is no intersection, the nearest point on the rectangle is returned.
* @return Intersection between a line and a rectangle.
*/
public static Optional<Point> getIntersection(Point lineOrigin, Point lineTerminus, IGraphicalEditPart part, boolean minimalDistancefromLineOrigin, boolean useNearestPoint) {
// Get the bounds of the part
Rectangle bounds = getAbsoluteBoundsIn100Percent(part);
return getIntersection(lineOrigin, lineTerminus, bounds, minimalDistancefromLineOrigin, useNearestPoint);
}
/**
* Get intersection between a line between lineOrigin and lineTerminus, and a rectangle. If there are several
* intersections, the shortest is returned.
*
* @param lineOrigin
* Origin of the line
* @param lineTerminus
* Terminus of the line
* @param rectangle
* rectangle to detect intersection.
* @param minimalDistancefromLineOrigin
* true if the shortest distance is between the line origin and the part, false otherwise.
* @return Intersection between a line and a rectangle.
*/
public static Optional<Point> getIntersection(Point lineOrigin, Point lineTerminus, Rectangle rectangle, boolean minimalDistancefromLineOrigin) {
return getIntersection(lineOrigin, lineTerminus, rectangle, minimalDistancefromLineOrigin, false);
}
/**
* Get intersection between a line between lineOrigin and lineTerminus, and a rectangle. If there are several
* intersections, the shortest is returned.
*
* @param lineOrigin
* Origin of the line
* @param lineTerminus
* Terminus of the line
* @param rectangle
* rectangle to detect intersection.
* @param minimalDistancefromLineOrigin
* true if the shortest distance is between the line origin and the part, false otherwise.
* @param useNearestPoint
* If true, if there is no intersection, the nearest point on the rectangle is returned.
* @return Intersection between a line and a rectangle.
*/
public static Optional<Point> getIntersection(Point lineOrigin, Point lineTerminus, Rectangle rectangle, boolean minimalDistancefromLineOrigin, boolean useNearestPoint) {
Optional<Point> result = Optional.empty();
// Create the line segment
PointList line = new PointList();
line.addPoint(lineOrigin);
line.addPoint(lineTerminus);
// Get the intersection
PointList partBoundsPointList = PointListUtilities.createPointsFromRect(rectangle);
PointList distances = new PointList();
PointList intersections = new PointList();
PointListUtilities.findIntersections(line, partBoundsPointList, intersections, distances);
Point oppositePoint;
if (minimalDistancefromLineOrigin) {
oppositePoint = lineOrigin;
} else {
oppositePoint = lineTerminus;
}
if (intersections.size() > 0) {
Point shortestPoint = intersections.getFirstPoint();
double minimalDistance = shortestPoint.getDistance(oppositePoint);
for (int i = 1; i < intersections.size(); i++) {
Point intersectionPoint = intersections.getPoint(i);
double currentDistance = intersectionPoint.getDistance(oppositePoint);
if (currentDistance < minimalDistance) {
minimalDistance = currentDistance;
shortestPoint = intersectionPoint;
}
}
result = Optional.of(shortestPoint);
} else if (!lineOrigin.equals(lineTerminus) && useNearestPoint) {
// If no intersection is found and the origin is not the terminus,
// the origin (or the terminus) is outside the rectangle, probably
// because of the snap, so we search the nearest point on the figure
// respecting one of the x or y coordinate.
Point linePointToConsider;
if (minimalDistancefromLineOrigin) {
linePointToConsider = lineTerminus;
} else {
linePointToConsider = lineOrigin;
}
List<Point> nearestPoints = findNearestPoints(partBoundsPointList, linePointToConsider);
if (nearestPoints.size() > 0) {
double minimalDistance = -1;
Point resultPoint = null;
// Search the closest intersection
for (Point nearestPoint : nearestPoints) {
double distance = nearestPoint.getDistance(oppositePoint);
if (minimalDistance == -1 || distance < minimalDistance) {
minimalDistance = distance;
resultPoint = nearestPoint;
}
}
result = Optional.ofNullable(resultPoint);
}
}
return result;
}
private static List<Point> findNearestPoints(PointList partBoundsPointList, Point linePointToConsider) {
List<Point> nearestPoints = new ArrayList<>();
List<?> rectangleBorders = PointListUtilities.getLineSegments(partBoundsPointList);
for (Object rectangleBorder : rectangleBorders) {
if (rectangleBorder instanceof LineSeg) {
LineSeg lineSeg = (LineSeg) rectangleBorder;
Point potentialNearestPoint;
if (lineSeg.getOrigin().x == lineSeg.getTerminus().x) {
potentialNearestPoint = new Point(lineSeg.getOrigin().x, linePointToConsider.y);
} else {
potentialNearestPoint = new Point(linePointToConsider.x, lineSeg.getOrigin().y);
}
if (lineSeg.containsPoint(potentialNearestPoint, 0)) {
nearestPoints.add(potentialNearestPoint);
}
}
}
return nearestPoints;
}
/**
* Get the intersection point list between the segment formed by the start and end points and the given figure
* bounds.
*
* @param start
* start point
* @param end
* end point
* @param figureBounds
* the figure bounds.
* @return the intersection point list.
*/
public static PointList getIntersectionPoints(Point start, Point end, Rectangle figureBounds) {
PointList intersection = new PointList();
final PointList polygon = PointListUtilities.createPointsFromRect(figureBounds);
LineSeg lineSeg = new LineSeg(start, end);
PointList intersectionTemp = lineSeg.getLineIntersectionsWithLineSegs(polygon);
for (int i = 0; i < intersectionTemp.size(); i++) {
Point currentPoint = intersectionTemp.getPoint(i);
if (lineSeg.containsPoint(currentPoint, 1)) {
intersection.addPoint(currentPoint);
}
}
return intersection;
}
/**
* Get the absolute bounds of this <code>part</code>.<BR>
* Detail: If the zoom is set to 200%, the location and the size are multiplied by two with respect to the real
* location and size.
*
* @param part
* The part to consider.
* @return The absolute bounds.
*/
public static Rectangle getAbsoluteBounds(IGraphicalEditPart part) {
PrecisionRectangle bounds;
if (part.getFigure() instanceof HandleBounds) {
bounds = new PrecisionRectangle(((HandleBounds) part.getFigure()).getHandleBounds());
} else {
bounds = new PrecisionRectangle(part.getFigure().getBounds());
}
part.getFigure().translateToAbsolute(bounds);
return bounds;
}
/**
* Get the absolute bounds of this <code>part</code>. In case of zoom or/and a scrollbar, the bounds are converted
* from screen to logical.<BR>
*
* @param part
* The part to consider.
* @return The absolute bounds.
*/
public static Rectangle getAbsoluteBoundsIn100Percent(GraphicalEditPart part) {
return getAbsoluteBoundsIn100Percent(part, false);
}
/**
* Get the absolute bounds of this <code>part</code>. In case of zoom or/and scrollbars, the bounds are converted
* from screen to logical.<BR>
*
* @param part
* The part to consider.
* @param considerAllScroll
* In all cases, the scroll of the diagram is used, but the scroll of containers are considered only if
* this parameter is true.
* @return The absolute bounds.
*/
public static Rectangle getAbsoluteBoundsIn100Percent(GraphicalEditPart part, boolean considerAllScroll) {
PrecisionRectangle bounds;
if (part.getFigure() instanceof HandleBounds) {
bounds = new PrecisionRectangle(((HandleBounds) part.getFigure()).getHandleBounds());
} else {
bounds = new PrecisionRectangle(part.getFigure().getBounds());
}
part.getFigure().translateToAbsolute(bounds);
screen2logical(bounds, part, considerAllScroll);
return bounds;
}
/**
* Get the absolute bounds of this <code>part</code> by excluding labels from the bounds.<br/>
* In case of zoom or/and scrollbars, the bounds are converted from screen to logical.<BR>
*
* @param part
* The part to consider.
* @return The absolute bounds.
*/
public static Rectangle getAbsoluteBoundsWithoutLabelsIn100Percent(GraphicalEditPart part) {
PrecisionRectangle bounds;
if (part.getFigure() instanceof IFigureWithoutLabels) {
bounds = new PrecisionRectangle(((IFigureWithoutLabels) part.getFigure()).getBoundsWithoutLabels());
part.getFigure().translateToAbsolute(bounds);
screen2logical(bounds, part, false);
} else {
bounds = new PrecisionRectangle(getAbsoluteBoundsIn100Percent(part));
}
return bounds;
}
/**
* Return true if the snapToGrid is enabled for the diagram containing this edit part, false otherwise.
*
* @param editPart
* The edit part to use.
* @return true if the snapToGrid is enabled for the diagram containing this edit part, false otherwise.
*/
public static boolean isSnapToGridEnabled(EditPart editPart) {
return (Boolean) editPart.getViewer().getProperty(SnapToGrid.PROPERTY_GRID_ENABLED);
}
/**
* Return the grid spacing in pixels for the diagram containing this edit part.
*
* @param editPart
* The edit part to use.
* @return the grid spacing in pixels.
*/
public static int getGridSpacing(EditPart editPart) {
return ((Dimension) editPart.getViewer().getProperty(SnapToGrid.PROPERTY_GRID_SPACING)).width;
}
}