/******************************************************************************* * Copyright (c) 2011 Association for Decentralized Information Management in * Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.diagram.connection; import gnu.trove.map.hash.THashMap; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.io.PrintStream; import java.io.Serializable; import java.util.ArrayList; import org.simantics.diagram.connection.RouteGraph.Interval; import org.simantics.diagram.connection.RouteGraph.IntervalCache; import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle; public class RouteTerminal extends RoutePoint implements RouteNode, Serializable { private static final long serialVersionUID = -8839093950347737029L; public static final int DIR_RIGHT = (1 << 0); public static final int DIR_DOWN = (1 << 1); public static final int DIR_LEFT = (1 << 2); public static final int DIR_UP = (1 << 3); public static final int DIR_DIRECT = (1 << 4); private Object data; private double minX, minY; private double maxX, maxY; private int allowedDirections; private ILineEndStyle style; private ILineEndStyle dynamicStyle; private boolean routeToBounds; private RouteTerminalPosition dynamicPosition; RouteLine line; public RouteTerminal(double x, double y, double minX, double minY, double maxX, double maxY, int allowedDirections, boolean routeToBounds, ILineEndStyle style, RouteTerminalPosition dynamicPosition) { super(x, y); this.minX = minX; this.minY = minY; this.maxX = maxX; this.maxY = maxY; this.allowedDirections = allowedDirections; this.routeToBounds = routeToBounds; this.style = style; this.dynamicPosition = dynamicPosition; } @Override public void setData(Object data) { this.data = data; } @Override public Object getData() { return data; } public int getAllowedDirections() { return allowedDirections; } public double getMinX() { return minX; } public double getMinY() { return minY; } public double getMaxX() { return maxX; } public double getMaxY() { return maxY; } public Rectangle2D getBounds() { return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY); } /** * Routes connection from the terminal to route line * adding necessary transient route lines. * @param cache */ protected void route(ArrayList lines, IntervalCache cache, boolean boundingBoxesIntersect) { if(routeToBounds) { int lineDir; boolean routeLineDoesNotIntersectTerminal; double linePosition = line.position; if(line.isHorizontal) { lineDir = linePosition < y ? 3 : 1; routeLineDoesNotIntersectTerminal = linePosition <= minY || linePosition >= maxY; } else { lineDir = linePosition < x ? 2 : 0; routeLineDoesNotIntersectTerminal = linePosition <= minX || linePosition >= maxX; } if(routeLineDoesNotIntersectTerminal) { RouteLine line0 = createLine0(lineDir); new RouteLink(line0, line); lines.add(line0); switch(lineDir) { case 0: x = maxX; y = 0.5*(minY+maxY); break; case 1: x = 0.5*(minX+maxX); y = maxY; break; case 2: x = minX; y = 0.5*(minY+maxY); break; case 3: x = 0.5*(minX+maxX); y = minY; break; } return; } else { if (!line.getPoints().contains(this)) line.addPoint(this); Interval interval = cache.get(line); if(line.isHorizontal) { if(interval.min < minX) x = minX; else x = maxX; y = linePosition; } else { x = linePosition; if(interval.min < minY) y = minY; else y = maxY; } } } else { // In which direction the route line is? int lineDir; boolean routeLineDoesNotIntersectTerminal; double linePosition = line.position; if(line.isHorizontal) { if (linePosition == y) { // direct route to terminal line.addPoint(this); return; } lineDir = linePosition < y ? 3 : 1; routeLineDoesNotIntersectTerminal = linePosition <= minY || linePosition >= maxY || boundingBoxesIntersect /* we ignore intersection in this case */; } else { if (linePosition == x) { // direct route to terminal line.addPoint(this); return; } lineDir = linePosition < x ? 2 : 0; routeLineDoesNotIntersectTerminal = linePosition <= minX || linePosition >= maxX || boundingBoxesIntersect /* we ignore intersection in this case */; } // We can route the connection directly to the right direction if((routeLineDoesNotIntersectTerminal || (line.isHorizontal && (x == minX || x == maxX)) || // already on the top/bottom edge (!line.isHorizontal && (y == minY || y == maxY)) // already on the left/right edge ) && Directions.isAllowed(allowedDirections, lineDir)) { RouteLine line0 = createLine0(lineDir); new RouteLink(line0, line); lines.add(line0); return; } // We must make one bend oneBend: { int dir = 1-(lineDir&1); if(Directions.isAllowed(allowedDirections, dir)) { if(Directions.isAllowed(allowedDirections, dir+2)) { Interval interval = cache.get(line); if(dir == 0) { if(interval.max <= maxX) dir = 2; } else /* dir == 1 */ { if(interval.max <= maxY) dir = 3; } } else { // ok } } else { if(Directions.isAllowed(allowedDirections, dir+2)) { dir = dir + 2; } else { break oneBend; } } RouteLine line0 = createLine0(dir); RouteLine line1 = createLine1(dir); new RouteLink(line0, line1); new RouteLink(line1, line); lines.add(line0); lines.add(line1); line0.nextTransient = line1; return; } // We can begin to the right direction but do two bends if(!routeLineDoesNotIntersectTerminal && Directions.isAllowed(allowedDirections, lineDir)) { RouteLine line0 = createLine0(lineDir); RouteLine line1 = createLine1(lineDir); RouteLine line2 = createLine2(lineDir, cache); new RouteLink(line0, line1); new RouteLink(line1, line2); new RouteLink(line2, line); lines.add(line0); lines.add(line1); lines.add(line2); line0.nextTransient = line1; line1.nextTransient = line2; return; } // Only allowed direction is to completely wrong direction: // we must make two bends { int dir = lineDir^2; RouteLine line0 = createLine0(dir); RouteLine line1 = createLine1(dir); RouteLine line2 = createLine2(dir, cache); new RouteLink(line0, line1); new RouteLink(line1, line2); new RouteLink(line2, line); lines.add(line0); lines.add(line1); lines.add(line2); line0.nextTransient = line1; line1.nextTransient = line2; return; } } } protected RouteLine createLine0(int dir) { RouteLine line0 = (dir&1) == 0 ? new RouteLine(true, y) : new RouteLine(false, x) ; line0.addPoint(this); line0.terminal = this; return line0; } private RouteLine createLine1(int dir) { RouteLine line1 = (dir&1) == 0 ? new RouteLine(false, (dir&2) == 0 ? maxX : minX) : new RouteLine(true, (dir&2) == 0 ? maxY : minY) ; line1.terminal = this; return line1; } private RouteLine createLine2(int dir, IntervalCache cache) { Interval interval = cache.get(line); RouteLine line2; if((dir&1) == 0) { double position; if(minY < interval.min) { if(maxY > interval.max) { position = 2*maxY-y-interval.max < interval.min+y-2*minY ? maxY : minY; } else { position = maxY; } } else { if(maxY > interval.max) { position = minY; } else { position = maxY-y < y-minY ? maxY : minY; } } line2 = new RouteLine(true, position); } else { double position; if(minX < interval.min) { if(maxX > interval.max) { position = 2*maxX-x-interval.max < interval.min+x-2*minX ? maxX : minX; } else { position = maxX; } } else { if(maxX > interval.max) { position = minX; } else { position = maxX-x < x-minX ? maxX : minX; } } line2 = new RouteLine(false, position); } line2.terminal = this; return line2; } public boolean isNear(double x2, double y2) { return minX <= x2 && x2 <= maxX && minY <= y2 && y2 <= maxY; } void setLocation(double x2, double y2) { double dx = x2 - x; double dy = y2 - y; x = x2; y = y2; minX += dx; minY += dy; maxX += dx; maxY += dy; } void rotate(int amount) { amount %= 4; if(amount < 0) amount += 4; int temp = (allowedDirections&15) << amount; allowedDirections = (temp&15) | (temp >> 4) | (allowedDirections&16); } public double approximatePositionToLine() { // In which direction the route line is? int lineDir = line.isHorizontal ? (line.position < y ? 3 : 1) : (line.position < x ? 2 : 0) ; // We can route the connection directly to the right direction if(Directions.isAllowed(allowedDirections, lineDir)) return line.isHorizontal ? x : y; // We must make one bend for(int dir = 0;dir < 4;++dir) { if(Directions.isAllowed(allowedDirections, dir) && ((dir^lineDir)&1) == 1) { switch(dir) { case 0: return maxX; case 1: return maxY; case 2: return minX; case 3: return minY; } } } // Only allowed direction is to completely wrong direction: // we must make two bends { // Approximation return line.isHorizontal ? x : y; } } public RouteTerminal copy(THashMap map) { RouteTerminal copy = (RouteTerminal)map.get(this); if(copy == null) { copy = new RouteTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, routeToBounds, style, dynamicPosition); copy.setDynamicStyle(dynamicStyle); map.put(this, copy); copy.data = data; copy.line = line == null ? null : line.copy(map); } return copy; } public void print(PrintStream out) { out.print(" (" + x + "," + y + ") " + allowedDirections + " -> "); if (line != null) line.print(out); else out.print("NO LINE"); out.print(" (data=" + data + ")"); out.println(); } public ILineEndStyle getStyle() { return style; } public ILineEndStyle getRenderStyle() { if (dynamicStyle != null) return dynamicStyle; return style; } public boolean hasDirectConnection() { return (allowedDirections&16) == 16; } public RouteLine getLine() { return line; } public void setLine(RouteLine line) { this.line = line; } public void setMinX(double minX) { this.minX = minX; } public void setMinY(double minY) { this.minY = minY; } public void setMaxX(double maxX) { this.maxX = maxX; } public void setMaxY(double maxY) { this.maxY = maxY; } public void toggleDirectLines() { this.allowedDirections ^= 16; } public boolean isRouteToBounds() { return routeToBounds; } public void setStyle(ILineEndStyle style) { this.style = style; } public ILineEndStyle getDynamicStyle() { return dynamicStyle; } public void setDynamicStyle(ILineEndStyle dynamicStyle) { this.dynamicStyle = dynamicStyle; } public RouteTerminalPosition getDynamicPosition() { return dynamicPosition; } public boolean updateDynamicPosition() { boolean changed = false; if (dynamicPosition != null) { AffineTransform tr = dynamicPosition.getTransform(); if (tr != null) { double nx = tr.getTranslateX(); changed |= x != nx; x = nx; double ny = tr.getTranslateY(); changed |= y != ny; y = ny; } } return changed; } }