1 /*******************************************************************************
\r
2 * Copyright (c) 2010, 2012 Association for Decentralized Information Management in
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.sysdyn.ui.elements.connections;
\r
14 import java.awt.BasicStroke;
\r
15 import java.awt.Color;
\r
16 import java.awt.Font;
\r
17 import java.awt.Graphics2D;
\r
18 import java.awt.Stroke;
\r
19 import java.awt.geom.Arc2D;
\r
20 import java.awt.geom.Path2D;
\r
21 import java.awt.geom.Point2D;
\r
22 import java.awt.geom.Rectangle2D;
\r
23 import java.beans.PropertyChangeEvent;
\r
24 import java.beans.PropertyChangeListener;
\r
25 import java.util.Collection;
\r
27 import org.simantics.diagram.elements.TextNode;
\r
28 import org.simantics.g2d.utils.Alignment;
\r
29 import org.simantics.scenegraph.ISelectionPainterNode;
\r
30 import org.simantics.scenegraph.g2d.IG2DNode;
\r
31 import org.simantics.scenegraph.g2d.events.EventTypes;
\r
32 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
33 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
\r
34 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
35 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
36 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
\r
37 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
38 import org.simantics.scenegraph.utils.NodeUtil;
\r
39 import org.simantics.sysdyn.ui.editor.routing.DependencyRouter;
\r
40 import org.simantics.utils.datastructures.Pair;
\r
43 * Node for dependency arrows and polarity text
\r
44 * @author Teemu Lempinen
\r
47 public class DependencyNode extends TextNode implements ISelectionPainterNode {
\r
49 public static final String INSIDE = "Inside";
\r
50 public static final String OUTSIDE = "Outside";
\r
52 public static final double HITMARGIN = 1.7;
\r
54 private static final long serialVersionUID = 1294351381209071074L;
\r
56 private static final BasicStroke STROKE = new BasicStroke(1.0f);
\r
58 private Color color;
\r
59 private Stroke stroke;
\r
60 private Rectangle2D beginBounds;
\r
61 private Rectangle2D endBounds;
\r
62 private double angle = 0.1;
\r
63 private String side;
\r
64 private transient Pair<Arc2D, Path2D> shapes = new Pair<Arc2D, Path2D>(new Arc2D.Double(), new Path2D.Double());
\r
66 transient public boolean hover = false;
\r
67 private boolean dragging = false;
\r
69 private transient PropertyChangeListener fieldListener = null;
\r
72 public void init() {
\r
74 addEventHandler(this);
\r
80 * Inits the dependency node with a text
\r
81 * @param text Polarity
\r
82 * @param side Polarity Location
\r
84 * @param color Color
\r
85 * @param x Text initial location x
\r
86 * @param y Text initial location y
\r
87 * @param scale Scale
\r
89 public void init(String text, String side, Font font, Color color, double x, double y, double scale) {
\r
90 super.init(text, font, color, x, y, scale);
\r
92 setHorizontalAlignment((byte) Alignment.CENTER.ordinal());
\r
93 setVerticalAlignment((byte) Alignment.CENTER.ordinal());
\r
97 public void cleanup() {
\r
101 public void setFieldListener(PropertyChangeListener listener) {
\r
102 this.fieldListener = listener;
\r
106 public void commitProperty(String field, Object value) {
\r
107 if(fieldListener != null) {
\r
108 fieldListener.propertyChange(new PropertyChangeEvent(this, field, null, value));
\r
112 @PropertySetter("color")
\r
113 @SyncField("color")
\r
114 public void setColor(Color color) {
\r
115 this.color = color;
\r
118 @PropertySetter("stroke")
\r
119 @SyncField("stroke")
\r
120 public void setStroke(Stroke stroke) {
\r
121 this.stroke = stroke;
\r
124 @PropertySetter("beginBounds")
\r
125 @SyncField("beginBounds")
\r
126 public void setBeginBounds(Rectangle2D beginBounds) {
\r
127 this.beginBounds = beginBounds;
\r
130 @PropertySetter("endBounds")
\r
131 @SyncField("endBounds")
\r
132 public void setEndBounds(Rectangle2D endBounds) {
\r
133 this.endBounds = endBounds;
\r
136 @PropertySetter("angle")
\r
137 @SyncField("angle")
\r
138 public void setAngle(Double angle) {
\r
139 this.angle = angle.doubleValue();
\r
140 if(this.beginBounds != null && this.endBounds != null)
\r
141 this.shapes = DependencyRouter.createArrowShape(this.shapes, this.beginBounds, this.endBounds, this.angle);
\r
144 @PropertySetter("shapes")
\r
145 @SyncField("shapes")
\r
146 public void setShapes(Pair<Arc2D, Path2D> shapes) {
\r
147 this.shapes = shapes;
\r
150 public Color getColor() {
\r
154 public Stroke getStroke() {
\r
158 public Rectangle2D getBeginBounds() {
\r
159 return beginBounds;
\r
162 public Rectangle2D getEndBounds() {
\r
166 public double getAngle() {
\r
170 public Pair<Arc2D, Path2D> getShapes() {
\r
177 public void render(Graphics2D g) {
\r
178 if(beginBounds == null || endBounds == null) return;
\r
180 // Removed to let the global control handle rendering quality issues.
\r
181 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
\r
183 boolean selected = NodeUtil.isSelected(this, 2);
\r
184 if(font != null) g.setFont(font);
\r
186 g.setColor(Color.PINK);
\r
187 g.setStroke(STROKE);
\r
188 g.draw(shapes.first);
\r
189 g.fill(shapes.second);
\r
190 if(color != null) g.setColor(color);
\r
191 g.setStroke(stroke);
\r
192 g.draw(shapes.first);
\r
193 g.fill(shapes.second);
\r
195 g.setColor(Color.LIGHT_GRAY);
\r
196 g.setStroke(STROKE);
\r
197 g.draw(shapes.first);
\r
198 g.fill(shapes.second);
\r
199 if(color != null) g.setColor(color);
\r
200 g.setStroke(stroke);
\r
201 g.draw(shapes.first);
\r
202 g.fill(shapes.second);
\r
204 if(color != null) g.setColor(color);
\r
205 if(stroke != null) g.setStroke(stroke);
\r
206 g.draw(shapes.first);
\r
207 g.fill(shapes.second);
\r
210 double angleRad = angle > 0 ?
\r
211 Math.toRadians(shapes.first.getAngleStart() + shapes.first.getAngleExtent()) :
\r
212 Math.toRadians(shapes.first.getAngleStart());
\r
213 Point2D point = angle > 0 ? shapes.first.getEndPoint() : shapes.first.getStartPoint();
\r
217 if(OUTSIDE.equals(side)) {
\r
221 double a = Math.toRadians(angle < 0 ? angle1 : angle2);
\r
222 double s = Math.sin(a) * 3;
\r
223 double c = Math.cos(a) * 4;
\r
225 g.translate(point.getX(), point.getY());
\r
226 g.rotate(-angleRad);
\r
228 g.rotate(angleRad);
\r
230 g.rotate(-angleRad);
\r
231 g.translate(-s, -c);
\r
232 g.rotate(angleRad);
\r
233 g.translate(-point.getX(), -point.getY());
\r
237 boolean pressHit = false;
\r
239 private boolean hitTest(org.simantics.scenegraph.g2d.events.MouseEvent event, double tolerance) {
\r
240 if(beginBounds == null || endBounds == null) return false;
\r
241 Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());
\r
242 return Arcs.hitTest(beginBounds, endBounds, angle, localPos.getX(), localPos.getY(), tolerance);
\r
245 protected double getRadialDistanse(Point2D coord) {
\r
246 if(beginBounds == null || endBounds == null) return Double.NaN;
\r
247 Point2D localPos = NodeUtil.worldToLocal(this, coord, new Point2D.Double());
\r
248 return Arcs.getRadialDistance(beginBounds, endBounds, angle, localPos.getX(), localPos.getY());
\r
252 public Rectangle2D getBoundsInLocal() {
\r
257 public int getEventMask() {
\r
258 return super.getEventMask() | EventTypes.MouseDragBeginMask
\r
259 | EventTypes.MouseButtonPressedMask
\r
260 | EventTypes.MouseButtonReleasedMask
\r
265 protected boolean mouseMoved(MouseMovedEvent event) {
\r
266 boolean hit = hitTest(event, HITMARGIN);
\r
268 Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());
\r
270 setAngle(Arcs.angleOfArc(
\r
271 beginBounds.getCenterX(), beginBounds.getCenterY(),
\r
272 localPos.getX(), localPos.getY(),
\r
273 endBounds.getCenterX(), endBounds.getCenterY()));
\r
277 if (hit != hover) {
\r
284 private static boolean isEventDummy(MouseDragBegin e) {
\r
285 if (e.controlPosition.distance(0, 0) == 0
\r
286 && e.screenPosition.distance(0, 0) == 0
\r
287 && e.buttons == 0) {
\r
295 protected boolean mouseDragged(MouseDragBegin e) {
\r
296 // Get rid of dummy events from dragGestureRecognized
\r
297 if (isEventDummy(e)) {
\r
300 //System.out.println(this.toString() + " event: " + e.toString());
\r
301 boolean selected = NodeUtil.isSelected(this, 2);
\r
302 double myRadialDistance = this.getRadialDistanse(e.controlPosition);
\r
303 Collection<?> nodes = this.getParent().getParent().getParent().getNodes();
\r
305 for (Object temp1 : nodes) {
\r
306 if (temp1 instanceof ConnectionNode) {
\r
307 for ( IG2DNode temp2 : ((ConnectionNode)temp1).getNodes()) {
\r
308 if (temp2 instanceof SingleElementNode) {
\r
309 for ( IG2DNode temp3 : ((SingleElementNode)temp2).getNodes()) {
\r
310 if (temp3 instanceof DependencyNode){
\r
311 DependencyNode otherDependencyNode = (DependencyNode)temp3;
\r
312 if (otherDependencyNode == this) {
\r
315 double otherNodeDist = otherDependencyNode.getRadialDistanse(e.controlPosition);
\r
316 if (Double.isNaN(otherNodeDist)) {
\r
319 if (otherDependencyNode.isDragging()) {
\r
322 if (NodeUtil.isSelected(otherDependencyNode, 2) && (otherNodeDist < HITMARGIN)) {
\r
325 if (otherNodeDist < myRadialDistance) {
\r
335 if ( (myRadialDistance < HITMARGIN) && !dragging) {
\r
343 protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
\r
347 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
\r
349 commitProperty("angle", angle);
\r
355 protected boolean isDragging() {
\r