1 /*******************************************************************************
\r
2 * Copyright (c) 2010, 2012, 2014 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.Shape;
\r
19 import java.awt.Stroke;
\r
20 import java.awt.geom.Arc2D;
\r
21 import java.awt.geom.Path2D;
\r
22 import java.awt.geom.Point2D;
\r
23 import java.awt.geom.Rectangle2D;
\r
24 import java.beans.PropertyChangeEvent;
\r
25 import java.beans.PropertyChangeListener;
\r
26 import java.util.Collection;
\r
27 import java.util.HashMap;
\r
29 import org.simantics.diagram.elements.TextNode;
\r
30 import org.simantics.g2d.utils.Alignment;
\r
31 import org.simantics.scenegraph.ISelectionPainterNode;
\r
32 import org.simantics.scenegraph.g2d.IG2DNode;
\r
33 import org.simantics.scenegraph.g2d.events.EventTypes;
\r
34 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
35 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
\r
36 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
37 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
38 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
\r
39 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
40 import org.simantics.scenegraph.utils.NodeUtil;
\r
41 import org.simantics.sysdyn.ui.editor.routing.DependencyRouter;
\r
42 import org.simantics.sysdyn.ui.elements.LoopNode.ILoopComponentNode;
\r
43 import org.simantics.sysdyn.ui.elements.LoopNode;
\r
44 import org.simantics.sysdyn.ui.elements.SysdynElementHints;
\r
45 import org.simantics.sysdyn.ui.utils.SysdynWorkbenchUtils;
\r
46 import org.simantics.utils.datastructures.Triple;
\r
49 * Node for dependency arrows and polarity text
\r
50 * @author Teemu Lempinen
\r
51 * @author Tuomas Miettinen
\r
54 public class DependencyNode extends TextNode implements ISelectionPainterNode, ILoopComponentNode {
\r
56 public static final String INSIDE = "Inside";
\r
57 public static final String OUTSIDE = "Outside";
\r
59 public static final double HITMARGIN = 1.7;
\r
61 private static final long serialVersionUID = 1294351381209071074L;
\r
63 private Color color;
\r
64 private Stroke stroke;
\r
65 private Shape beginBounds;
\r
66 private Shape endBounds;
\r
67 private double angle = 0.3;
\r
68 private String side;
\r
69 private boolean delayMark = false;
\r
70 private boolean arrowHead = true;
\r
71 private transient Triple<Arc2D, Path2D, Path2D> shapes = new Triple<Arc2D, Path2D, Path2D>(new Arc2D.Double(), new Path2D.Double(), new Path2D.Double());
\r
73 transient public boolean hover = false;
\r
74 private boolean dragging = false;
\r
76 private transient PropertyChangeListener fieldListener = null;
\r
79 public void init() {
\r
81 addEventHandler(this);
\r
87 * Inits the dependency node with a text
\r
88 * @param text Polarity
\r
89 * @param side Polarity Location
\r
91 * @param color Color
\r
92 * @param x Text initial location x
\r
93 * @param y Text initial location y
\r
94 * @param scale Scale
\r
96 public void init(String text, String side, boolean delayMark, boolean arrowHead, Font font, Color color, double x, double y, double scale) {
\r
97 super.init(text, font, color, x, y, scale);
\r
99 this.delayMark = delayMark;
\r
100 this.arrowHead = arrowHead;
\r
101 setHorizontalAlignment((byte) Alignment.CENTER.ordinal());
\r
102 setVerticalAlignment((byte) Alignment.CENTER.ordinal());
\r
106 public void cleanup() {
\r
110 public void setFieldListener(PropertyChangeListener listener) {
\r
111 this.fieldListener = listener;
\r
115 public void commitProperty(String field, Object value) {
\r
116 if(fieldListener != null) {
\r
117 fieldListener.propertyChange(new PropertyChangeEvent(this, field, null, value));
\r
121 @PropertySetter("color")
\r
122 @SyncField("color")
\r
123 public void setColor(Color color) {
\r
124 this.color = color;
\r
127 @PropertySetter("stroke")
\r
128 @SyncField("stroke")
\r
129 public void setStroke(Stroke stroke) {
\r
130 this.stroke = stroke;
\r
133 @PropertySetter("beginBounds")
\r
134 @SyncField("beginBounds")
\r
135 public void setBeginBounds(Shape beginBounds) {
\r
136 this.beginBounds = beginBounds;
\r
139 @PropertySetter("endBounds")
\r
140 @SyncField("endBounds")
\r
141 public void setEndBounds(Shape endBounds) {
\r
142 this.endBounds = endBounds;
\r
145 @PropertySetter("angle")
\r
146 @SyncField("angle")
\r
147 public void setAngle(Double angle) {
\r
148 this.angle = angle.doubleValue();
\r
149 if(this.beginBounds != null && this.endBounds != null)
\r
150 this.shapes = DependencyRouter.createArrowShape(this.shapes, this.beginBounds, this.endBounds, this.angle, this.stroke);
\r
153 @PropertySetter("shapes")
\r
154 @SyncField("shapes")
\r
155 public void setShapes(Triple<Arc2D, Path2D, Path2D> shapes) {
\r
156 this.shapes = shapes;
\r
159 public Color getColor() {
\r
163 public Stroke getStroke() {
\r
167 public Shape getBeginBounds() {
\r
168 return beginBounds;
\r
171 public Shape getEndBounds() {
\r
175 public double getAngle() {
\r
179 public Triple<Arc2D, Path2D, Path2D> getShapes() {
\r
186 public void render(Graphics2D g) {
\r
187 if(beginBounds == null || endBounds == null) return;
\r
189 // Removed to let the global control handle rendering quality issues.
\r
190 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
\r
192 boolean selected = NodeUtil.isSelected(this, 2);
\r
193 if(font != null) g.setFont(font);
\r
195 g.setColor(Color.PINK);
\r
196 float strokeWidth = 1.4f + 2 * (stroke instanceof BasicStroke ? ((BasicStroke)stroke).getLineWidth() : DependencyEdgeClass.DEFAULT_STROKE_WIDTH);
\r
197 g.setStroke(new BasicStroke(strokeWidth));
\r
198 g.draw(shapes.first);
\r
199 g.fill(shapes.second);
\r
200 if(color != null) g.setColor(color);
\r
201 g.setStroke(stroke);
\r
202 g.draw(shapes.first);
\r
204 g.draw(shapes.second);
\r
205 g.fill(shapes.second);
\r
207 if (delayMark) g.draw(shapes.third);
\r
209 g.setColor(Color.LIGHT_GRAY);
\r
210 float strokeWidth = 1.4f + 2 * (stroke instanceof BasicStroke ? ((BasicStroke)stroke).getLineWidth() : DependencyEdgeClass.DEFAULT_STROKE_WIDTH);
\r
211 g.setStroke(new BasicStroke(strokeWidth));
\r
212 g.draw(shapes.first);
\r
213 g.fill(shapes.second);
\r
214 if(color != null) g.setColor(color);
\r
215 g.setStroke(stroke);
\r
216 g.draw(shapes.first);
\r
218 g.draw(shapes.second);
\r
219 g.fill(shapes.second);
\r
221 if (delayMark) g.draw(shapes.third);
\r
222 } else if (isLoopSelected()) {
\r
223 g.setColor(LoopNode.HIGHLIGHT_COLOR);
\r
224 if(stroke != null) g.setStroke(stroke);
\r
225 g.draw(shapes.first);
\r
227 g.draw(shapes.second);
\r
228 g.fill(shapes.second);
\r
230 if (delayMark) g.draw(shapes.third);
\r
232 if(color != null) g.setColor(color);
\r
233 if(stroke != null) g.setStroke(stroke);
\r
234 g.draw(shapes.first);
\r
236 g.draw(shapes.second);
\r
237 g.fill(shapes.second);
\r
239 if (delayMark) g.draw(shapes.third);
\r
242 double angleRad = angle > 0 ?
\r
243 Math.toRadians(shapes.first.getAngleStart() + shapes.first.getAngleExtent()) :
\r
244 Math.toRadians(shapes.first.getAngleStart());
\r
245 Point2D point = angle > 0 ? shapes.first.getEndPoint() : shapes.first.getStartPoint();
\r
249 if(OUTSIDE.equals(side)) {
\r
253 double a = Math.toRadians(angle < 0 ? angle1 : angle2);
\r
254 double s = Math.sin(a) * 3;
\r
255 double c = Math.cos(a) * 4;
\r
257 g.translate(point.getX(), point.getY());
\r
258 g.rotate(-angleRad);
\r
260 g.rotate(angleRad);
\r
262 g.rotate(-angleRad);
\r
263 g.translate(-s, -c);
\r
264 g.rotate(angleRad);
\r
265 g.translate(-point.getX(), -point.getY());
\r
269 boolean pressHit = false;
\r
270 private HashMap<LoopNode, Boolean> loopSelectionMap = new HashMap<LoopNode, Boolean>();
\r
272 private boolean isLoopSelected() {
\r
273 return loopSelectionMap.containsValue(true);
\r
276 protected boolean hitTest(org.simantics.scenegraph.g2d.events.MouseEvent event, double tolerance) {
\r
277 if(beginBounds == null || endBounds == null) return false;
\r
278 Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());
\r
279 return Arcs.hitTest(beginBounds, endBounds, angle, localPos.getX(), localPos.getY(), tolerance);
\r
282 protected double getRadialDistanse(Point2D coord) {
\r
283 if(beginBounds == null || endBounds == null) return Double.NaN;
\r
284 Point2D localPos = NodeUtil.worldToLocal(this, coord, new Point2D.Double());
\r
285 return Arcs.getRadialDistance(beginBounds, endBounds, angle, localPos.getX(), localPos.getY());
\r
289 public Rectangle2D getBoundsInLocal() {
\r
294 public int getEventMask() {
\r
295 return super.getEventMask() | EventTypes.MouseDragBeginMask
\r
296 | EventTypes.MouseButtonPressedMask
\r
297 | EventTypes.MouseButtonReleasedMask
\r
302 protected boolean mouseMoved(MouseMovedEvent event) {
\r
303 boolean hit = hitTest(event, HITMARGIN);
\r
305 Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());
\r
307 setAngle(Arcs.angleOfArc(
\r
308 beginBounds.getBounds2D().getCenterX(), beginBounds.getBounds2D().getCenterY(),
\r
309 localPos.getX(), localPos.getY(),
\r
310 endBounds.getBounds2D().getCenterX(), endBounds.getBounds2D().getCenterY()));
\r
314 if (hit != hover) {
\r
321 private static boolean isEventDummy(MouseDragBegin e) {
\r
322 if (e.controlPosition.distance(0, 0) == 0
\r
323 && e.screenPosition.distance(0, 0) == 0
\r
324 && e.buttons == 0) {
\r
332 protected boolean mouseDragged(MouseDragBegin e) {
\r
333 // Get rid of dummy events from dragGestureRecognized
\r
334 if (isEventDummy(e)) {
\r
338 // Disable dragging if LockSketch is ON
\r
339 if (SysdynElementHints.LOCK_TOOL.equals(SysdynWorkbenchUtils.getSysdynToolMode()))
\r
342 //System.out.println(this.toString() + " event: " + e.toString());
\r
343 boolean selected = NodeUtil.isSelected(this, 2);
\r
344 double myRadialDistance = this.getRadialDistanse(e.controlPosition);
\r
345 Collection<?> nodes = this.getParent().getParent().getParent().getNodes();
\r
347 for (Object temp1 : nodes) {
\r
348 if (temp1 instanceof ConnectionNode) {
\r
349 for ( IG2DNode temp2 : ((ConnectionNode)temp1).getNodes()) {
\r
350 if (temp2 instanceof SingleElementNode) {
\r
351 for ( IG2DNode temp3 : ((SingleElementNode)temp2).getNodes()) {
\r
352 if (temp3 instanceof DependencyNode){
\r
353 DependencyNode otherDependencyNode = (DependencyNode)temp3;
\r
354 if (otherDependencyNode == this) {
\r
357 double otherNodeDist = otherDependencyNode.getRadialDistanse(e.controlPosition);
\r
358 if (Double.isNaN(otherNodeDist)) {
\r
361 if (otherDependencyNode.isDragging()) {
\r
364 if (NodeUtil.isSelected(otherDependencyNode, 2) && (otherNodeDist < HITMARGIN)) {
\r
367 if (otherNodeDist < myRadialDistance) {
\r
377 if ( (myRadialDistance < HITMARGIN) && !dragging) {
\r
385 protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
\r
389 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
\r
391 commitProperty("angle", angle);
\r
397 protected boolean isDragging() {
\r
402 public void setLoopSelected(LoopNode loop, boolean selected) {
\r
403 Boolean loopSelected = loopSelectionMap.get(loop);
\r
404 if (loopSelected == null || loopSelected != selected) {
\r
405 loopSelectionMap.put(loop, selected);
\r