1 /*******************************************************************************
2 * Copyright (c) 2011 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.connection.rendering;
14 import java.awt.Color;
15 import java.awt.Graphics2D;
16 import java.awt.RenderingHints;
17 import java.awt.Stroke;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Ellipse2D;
20 import java.awt.geom.FlatteningPathIterator;
21 import java.awt.geom.Line2D;
22 import java.awt.geom.Path2D;
23 import java.awt.geom.PathIterator;
24 import java.awt.geom.Point2D;
25 import java.io.Serializable;
28 * @author Tuukka Lehtonen
30 public class BasicConnectionStyle implements ConnectionStyle, Serializable {
32 private static final long serialVersionUID = -5799681720482456895L;
34 // line thickness in millimeters.
35 final Color lineColor;
36 final Color branchPointColor;
37 final double branchPointRadius;
38 final Stroke lineStroke;
39 final Stroke routeLineStroke;
40 final double degenerateLineLength;
41 final double rounding;
44 transient Line2D line = new Line2D.Double();
45 transient Ellipse2D ellipse = new Ellipse2D.Double();
47 public BasicConnectionStyle(Color lineColor, Color branchPointColor, double branchPointRadius, Stroke lineStroke, Stroke routeLineStroke, double degenerateLineLength,
48 double rounding, double offset) {
49 this.lineColor = lineColor;
50 this.branchPointColor = branchPointColor;
51 this.branchPointRadius = branchPointRadius;
52 this.lineStroke = lineStroke;
53 this.routeLineStroke = routeLineStroke;
54 this.degenerateLineLength = degenerateLineLength;
55 this.rounding = rounding;
59 public BasicConnectionStyle(Color lineColor, Color branchPointColor, double branchPointRadius, Stroke lineStroke, Stroke routeLineStroke, double degenerateLineLength,
61 this(lineColor, branchPointColor, branchPointRadius, lineStroke, routeLineStroke, degenerateLineLength, rounding, 0.0);
64 public BasicConnectionStyle(Color lineColor, Color branchPointColor, double branchPointRadius, Stroke lineStroke, Stroke routeLineStroke, double degenerateLineLength) {
65 this(lineColor, branchPointColor, branchPointRadius, lineStroke, routeLineStroke, degenerateLineLength, 0.0, 0.0);
68 public Color getLineColor() {
72 public Color getBranchPointColor() {
73 return branchPointColor;
76 public double getBranchPointRadius() {
77 return branchPointRadius;
80 public Stroke getLineStroke() {
84 public Stroke getRouteLineStroke() {
85 return routeLineStroke;
89 public void drawLine(Graphics2D g, double x1, double y1, double x2,
90 double y2, boolean isTransient) {
91 if (lineColor != null)
92 g.setColor(lineColor);
94 g.setStroke(lineStroke);
95 line.setLine(x1, y1, x2, y2);
98 g.setStroke(routeLineStroke);
99 line.setLine(x1, y1, x2, y2);
105 public void drawPath(Graphics2D g, Path2D path, boolean isTransient) {
106 if (lineColor != null)
107 g.setColor(lineColor);
108 if (lineStroke != null)
109 g.setStroke(lineStroke);
111 Object oldRenderingHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
112 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
115 path = offsetPath(path, offset);
118 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldRenderingHint);
122 path = offsetPath(path, offset);
128 private static Point2D getNormal(Point2D dir) {
129 return new Point2D.Double(-dir.getY(), dir.getX());
132 private static Point2D normalize(Point2D v) {
133 double d = Math.sqrt(v.getX() * v.getX() + v.getY() * v.getY());
134 v.setLocation(v.getX() / d, v.getY() / d);
138 private static Path2D offsetPath(Path2D path, double offset) {
139 Path2D result = new Path2D.Double();
140 PathIterator iter = new FlatteningPathIterator(path.getPathIterator(null), 0.05, 10);
142 double c[] = new double[6];
145 boolean first = true;
146 Point2D prevDir = null;
147 Point2D prevPos = null;
149 while (!iter.isDone()) {
150 int i = iter.currentSegment(c);
152 case PathIterator.SEG_MOVETO:
158 if (prevDir != null) {
159 Point2D N = normalize(getNormal(prevDir));
160 result.lineTo(prevPos.getX() + N.getX() * offset , prevPos.getY() + N.getY() * offset);
162 prevPos = new Point2D.Double(c[0], c[1]);
165 case PathIterator.SEG_LINETO:
166 case PathIterator.SEG_CLOSE:
167 if (i == PathIterator.SEG_CLOSE) {
171 Point2D currentDir = new Point2D.Double(c[0] - prevPos.getX(), c[1] - prevPos.getY());
172 if (currentDir.getX() == 0.0 && currentDir.getY() == 0) break;
174 if (prevDir == null) {
175 Point2D N = normalize(getNormal(currentDir));
176 result.moveTo(prevPos.getX() + N.getX() * offset, prevPos.getY() + N.getY() * offset);
177 prevPos = new Point2D.Double(c[0], c[i]);
178 prevDir = currentDir;
180 Point2D N1 = normalize(getNormal(prevDir));
181 Point2D N2 = normalize(getNormal(currentDir));
182 Point2D N = normalize(new Point2D.Double(N1.getX() + N2.getX(), N1.getY() + N2.getY()));
183 double dot = N1.getX() * N.getX() + N1.getY() * N.getY();
185 if (!Double.isFinite(dot) || Math.abs(dot) < 0.1) {
186 result.lineTo(prevPos.getX() + (N1.getX() + N1.getY()) * offset, prevPos.getY() + (N1.getY() - N1.getX()) * offset);
187 result.lineTo(prevPos.getX() + (N2.getX() + N1.getY()) * offset, prevPos.getY() + (N2.getY() - N1.getX()) * offset);
188 prevPos = new Point2D.Double(c[0], c[i]);
189 prevDir = currentDir;
191 double Nx = N.getX() * offset / dot;
192 double Ny = N.getY() * offset / dot;
193 result.lineTo(prevPos.getX() + Nx, prevPos.getY() + Ny);
194 prevPos = new Point2D.Double(c[0], c[i]);
195 prevDir = currentDir;
203 if (prevDir != null) {
204 Point2D N = normalize(getNormal(prevDir));
205 result.lineTo(prevPos.getX() + N.getX() * offset , prevPos.getY() + N.getY() * offset);
210 private Path2D round(Path2D path) {
211 Path2D newPath = new Path2D.Double();
212 PathIterator it = path.getPathIterator(new AffineTransform());
213 double[] coords = new double[6];
214 double newX=0.0, newY=0.0;
215 double curX=0.0, curY=0.0;
216 double oldX=0.0, oldY=0.0;
218 while(!it.isDone()) {
219 int type = it.currentSegment(coords);
220 if(type == PathIterator.SEG_LINETO) {
224 double dx1 = curX-oldX;
225 double dy1 = curY-oldY;
226 double dx2 = curX-newX;
227 double dy2 = curY-newY;
228 double r1 = Math.sqrt(dx1*dx1 + dy1*dy1);
229 double r2 = Math.sqrt(dx2*dx2 + dy2*dy2);
230 double maxRadius = 0.5 * Math.min(r1, r2);
231 double radius = Math.min(rounding, maxRadius);
232 double dx1Normalized = r1 > 0 ? dx1 / r1 : 0;
233 double dy1Normalized = r1 > 0 ? dy1 / r1 : 0;
234 double dx2Normalized = r2 > 0 ? dx2 / r2 : 0;
235 double dy2Normalized = r2 > 0 ? dy2 / r2 : 0;
236 newPath.lineTo(curX - radius*dx1Normalized, curY - radius*dy1Normalized);
237 newPath.curveTo(curX, curY,
239 curX - radius*dx2Normalized, curY - radius*dy2Normalized);
250 newPath.lineTo(curX, curY);
254 case PathIterator.SEG_MOVETO:
257 newPath.moveTo(curX, curY);
259 case PathIterator.SEG_QUADTO:
262 newPath.quadTo(coords[0], coords[1], coords[2], coords[3]);
264 case PathIterator.SEG_CUBICTO:
267 newPath.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
269 case PathIterator.SEG_CLOSE:
277 newPath.lineTo(curX, curY);
282 public void drawBranchPoint(Graphics2D g, double x, double y) {
283 g.setColor(branchPointColor);
284 double r = branchPointRadius;
286 ellipse.setFrame(x-r, y-r, d, d);
291 public void drawDegeneratedLine(Graphics2D g, double x, double y,
292 boolean isHorizontal, boolean isTransient) {
293 double d = getDegeneratedLineLength()*0.5;
295 line.setLine(x-d, y, x+d, y);
298 line.setLine(x, y-d, x, y+d);
304 public double getDegeneratedLineLength() {
305 return degenerateLineLength;
309 public int hashCode() {
310 final int prime = 31;
312 result = prime * result + ((branchPointColor == null) ? 0 : branchPointColor.hashCode());
314 temp = Double.doubleToLongBits(branchPointRadius);
315 result = prime * result + (int) (temp ^ (temp >>> 32));
316 temp = Double.doubleToLongBits(degenerateLineLength);
317 result = prime * result + (int) (temp ^ (temp >>> 32));
318 result = prime * result + ((lineColor == null) ? 0 : lineColor.hashCode());
319 result = prime * result + ((lineStroke == null) ? 0 : lineStroke.hashCode());
320 temp = Double.doubleToLongBits(rounding);
321 result = prime * result + (int) (temp ^ (temp >>> 32));
322 result = prime * result + ((routeLineStroke == null) ? 0 : routeLineStroke.hashCode());
327 public boolean equals(Object obj) {
332 if (getClass() != obj.getClass())
334 BasicConnectionStyle other = (BasicConnectionStyle) obj;
335 if (branchPointColor == null) {
336 if (other.branchPointColor != null)
338 } else if (!branchPointColor.equals(other.branchPointColor))
340 if (Double.doubleToLongBits(branchPointRadius) != Double.doubleToLongBits(other.branchPointRadius))
342 if (Double.doubleToLongBits(degenerateLineLength) != Double.doubleToLongBits(other.degenerateLineLength))
344 if (lineColor == null) {
345 if (other.lineColor != null)
347 } else if (!lineColor.equals(other.lineColor))
349 if (lineStroke == null) {
350 if (other.lineStroke != null)
352 } else if (!lineStroke.equals(other.lineStroke))
354 if (Double.doubleToLongBits(rounding) != Double.doubleToLongBits(other.rounding))
356 if (routeLineStroke == null) {
357 if (other.routeLineStroke != null)
359 } else if (!routeLineStroke.equals(other.routeLineStroke))
364 public double getRounding() {
368 public double getOffset() {