/******************************************************************************* * MontiCore Language Workbench * Copyright (c) 2015, 2016, MontiCore, All rights reserved. * * This project is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this project. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package de.monticore.genericgraphics.view.figures.connections.locators; import java.util.ArrayList; import java.util.List; import org.eclipse.draw2d.ArrowLocator; import org.eclipse.draw2d.Connection; import org.eclipse.draw2d.ConnectionAnchor; import org.eclipse.draw2d.ConnectionLocator; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.RotatableDecoration; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; import org.eclipse.draw2d.geometry.Rectangle; /** * <p> * A {@link ArrowLocator} implementation for {@link Connection Connections} that * use a {@link EndPointFigureLocator}. This Locator implementation sets the * arrow location to the {@link IFigure} used and not directly at the source * anchor position. * </p> * <p> * Note: Works only for {@link ConnectionLocator#SOURCE} and * {@link ConnectionLocator#TARGET} as location. * * @author Tim Enger */ public class FigureArrowLocator extends ArrowLocator { private IFigure figure; /** * Constructor * * @param connection The {@link Connection} associated with the locator * @param location The location of the arrow decoration. Only * {@link ConnectionLocator#SOURCE} and * {@link ConnectionLocator#TARGET} permitted. * @param figure The {@link IFigure} before which the arrow should be drawn */ public FigureArrowLocator(Connection connection, int location, IFigure figure) { super(connection, location); this.figure = figure; } /** * Relocates the passed in figure (which must be a {@link RotatableDecoration} * ) at either the start or end of the connection. * * @param target The RotatableDecoration to relocate */ @Override public void relocate(IFigure target) { PointList points = getConnection().getPoints(); RotatableDecoration arrow = (RotatableDecoration) target; if (figure != null) { // use the point, where the connection intersects with the figure Point inter = getIntersectionPoint(); arrow.setLocation(inter); } else { arrow.setLocation(getLocation(points)); } if (getAlignment() == SOURCE) { arrow.setReferencePoint(points.getPoint(1)); } else if (getAlignment() == TARGET) { arrow.setReferencePoint(points.getPoint(points.size() - 2)); } } /** * @return The intersection point of the connection with the figure */ private Point getIntersectionPoint() { PointList points = getConnection().getPoints(); Rectangle figB = figure.getBounds(); ConnectionAnchor anchor; Point anchorPoint; if (isSource()) { anchor = getConnection().getSourceAnchor(); anchorPoint = points.getFirstPoint(); } else { anchor = getConnection().getTargetAnchor(); anchorPoint = points.getLastPoint(); } // compute intersections with all 3 edges of the figure // that could possibly intersect with the connection // therefore first compute where the figure is relative to the anchor figure int position = PositionUtil.computePosition(anchor.getOwner().getBounds(), anchorPoint); switch (position) { case PositionConstants.NORTH: // figure is above, so check top, right & left border return checkBorders(figB, true, true, false, true, PositionConstants.NORTH); case PositionConstants.EAST: // figure is right, so check top, bottom & right border return checkBorders(figB, true, true, true, false, PositionConstants.EAST); case PositionConstants.SOUTH: // figure is bottom, so check bottom, right & left border return checkBorders(figB, false, true, true, true, PositionConstants.SOUTH); case PositionConstants.WEST: // figure is right, so check top, bottom & right border return checkBorders(figB, true, false, true, true, PositionConstants.WEST); } assert false : "FigureArrowLocator: This should not happen: invalid PositionConstant"; return new Point(0, 0); } private Point checkBorders(Rectangle figB, boolean checkTop, boolean checkRight, boolean checkBottom, boolean checkLeft, int position) { List<Point> points = new ArrayList<Point>(); if (checkTop) { // check if it is on the top border Point top = getIntersectionPointWithLabel(PositionConstants.NORTH); if (top.y == figB.getTopLeft().y && top.x >= figB.x && top.x <= figB.x + figB.width) { points.add(top); } } if (checkRight) { // check if it is on the right border Point right = getIntersectionPointWithLabel(PositionConstants.EAST); if (right.x == figB.getTopRight().x && right.y >= figB.y && right.y <= figB.y + figB.height) { points.add(right); } } if (checkBottom) { Point bottom = getIntersectionPointWithLabel(PositionConstants.SOUTH); if (bottom.y == figB.getBottomLeft().y && bottom.x >= figB.x && bottom.x <= figB.x + figB.width) { points.add(bottom); } } if (checkLeft) { Point left = getIntersectionPointWithLabel(PositionConstants.WEST); if (left.x == figB.getTopLeft().x && left.y >= figB.y && left.y <= figB.y + figB.height) { points.add(left); } } // if only one point is found, // we're happy, so just return it if (points.size() == 1) { return points.get(0); } // if there are two points // this means, the connection intersect with the // left & right side or bottom & top side if (points.size() == 2) { Point p1 = points.get(0); Point p2 = points.get(1); switch (position) { case PositionConstants.NORTH: // the figure is placed on the top side // so chose the upper of both position int minY = Math.min(p1.y, p2.y); return minY == p1.y ? p1 : p2; case PositionConstants.EAST: // the figure is placed on the right side // so chose the most right of both position int maxX = Math.max(p1.x, p2.x); return maxX == p1.x ? p1 : p2; case PositionConstants.SOUTH: // the figure is placed on the bottom side // so chose the lower of both position int maxY = Math.max(p1.y, p2.y); return maxY == p1.y ? p1 : p2; case PositionConstants.WEST: // the figure is placed on the left side // so chose the most left of both position int minX = Math.min(p1.x, p2.x); return minX == p1.x ? p1 : p2; } } assert false : "FigureArrowLocator: This should not happen: no intersection found"; return new Point(0, 0); } private boolean isSource() { if (getAlignment() == SOURCE) { return true; } return false; } private Point getIntersectionPointWithLabel(int line) { PointList points = getConnection().getPoints(); Rectangle l = figure.getBounds(); int x1 = 0; int y1 = 0; int x2 = 0; int y2 = 0; switch (line) { case PositionConstants.NORTH: // take the top border line of the label x1 = l.x; y1 = l.y; x2 = l.x + l.width; y2 = l.y; break; case PositionConstants.EAST: // the the right border line of the label x1 = l.x + l.width; y1 = l.y; x2 = l.x + l.width; y2 = l.y + l.height; break; case PositionConstants.SOUTH: // take the bottom border line of the label x1 = l.x; y1 = l.y + l.height; x2 = l.x + l.width; y2 = l.y + l.height; break; case PositionConstants.WEST: // the the left border line of the label x1 = l.x; y1 = l.y; x2 = l.x; y2 = l.y + l.height; break; } int x3 = getLocation(points).x; int y3 = getLocation(points).y; int x4, y4; Point p; if (isSource()) { p = points.getPoint(1); } else { p = points.getPoint(points.size() - 2); } x4 = p.x; y4 = p.y; return getIntersection(x1, y1, x2, y2, x3, y3, x4, y4); } /** * Computes the point of intersection between the two line l1 -> l2 and l3 -> * l4 where the points are given by * <ul> * <li>l1: (x1,y1)</li> * <li>l2: (x2,y2)</li> * <li>l1: (x3,y3)</li> * <li>l1: (x4,y4)</li> * </ul> * Return 0 if the lines are parallel.<br> * Note: the lines are not limited in this computation, so you'll always get * an intersection point, if not parallel. * * @param x1 * @param y1 * @param x2 * @param y2 * @param x3 * @param y3 * @param x4 * @param y4 * @return The intersecting point. */ private Point getIntersection(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) { double px1 = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4); double px2 = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); double px = px1 / px2; double py1 = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4); double py2 = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); double py = py1 / py2; return new Point((int) Math.round(px), (int) Math.round(py)); } }