]> gerrit.simantics Code Review - simantics/sysdyn.git/blob
0eacd1b7050e3e3bd9fde3c20f603d41f420294e
[simantics/sysdyn.git] /
1 /*******************************************************************************\r
2  * Copyright (c) 2010, 2012 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\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
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.sysdyn.ui.elements.connections;\r
13 \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
26 \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
41 \r
42 /**\r
43  * Node for dependency arrows and polarity text\r
44  * @author Teemu Lempinen\r
45  *\r
46  */\r
47 public class DependencyNode extends TextNode implements ISelectionPainterNode {\r
48 \r
49     public static final String INSIDE = "Inside";\r
50     public static final String OUTSIDE = "Outside";\r
51 \r
52     public static final double HITMARGIN = 1.7;\r
53     \r
54     private static final long serialVersionUID = 1294351381209071074L;\r
55 \r
56     private static final BasicStroke STROKE = new BasicStroke(1.0f);\r
57 \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
65 \r
66     transient public boolean hover = false;\r
67     private boolean dragging = false;\r
68 \r
69     private transient PropertyChangeListener fieldListener = null;\r
70 \r
71     @Override\r
72     public void init() {\r
73         super.init();\r
74         addEventHandler(this);\r
75 \r
76     }\r
77 \r
78 \r
79     /**\r
80      * Inits the dependency node with a text\r
81      * @param text Polarity\r
82      * @param side Polarity Location\r
83      * @param font Font\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
88      */\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
91         this.side = side;\r
92         setHorizontalAlignment((byte) Alignment.CENTER.ordinal());\r
93         setVerticalAlignment((byte) Alignment.CENTER.ordinal());\r
94     }\r
95 \r
96     @Override\r
97     public void cleanup() {\r
98         super.cleanup();\r
99     }\r
100 \r
101     public void setFieldListener(PropertyChangeListener listener) {\r
102         this.fieldListener = listener;\r
103     }\r
104 \r
105     @ServerSide\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
109         }\r
110     }\r
111 \r
112     @PropertySetter("color")\r
113     @SyncField("color")\r
114     public void setColor(Color color) {\r
115         this.color = color;\r
116     }\r
117 \r
118     @PropertySetter("stroke")\r
119     @SyncField("stroke")\r
120     public void setStroke(Stroke stroke) {\r
121         this.stroke = stroke;\r
122     }\r
123 \r
124     @PropertySetter("beginBounds")\r
125     @SyncField("beginBounds")\r
126     public void setBeginBounds(Rectangle2D beginBounds) {\r
127         this.beginBounds = beginBounds;\r
128     }\r
129 \r
130     @PropertySetter("endBounds")\r
131     @SyncField("endBounds")\r
132     public void setEndBounds(Rectangle2D endBounds) {\r
133         this.endBounds = endBounds;\r
134     }\r
135 \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
142     }\r
143 \r
144     @PropertySetter("shapes")\r
145     @SyncField("shapes")\r
146     public void setShapes(Pair<Arc2D, Path2D> shapes) {\r
147         this.shapes = shapes;\r
148     }\r
149 \r
150     public Color getColor() {\r
151         return color;\r
152     }\r
153 \r
154     public Stroke getStroke() {\r
155         return stroke;\r
156     }\r
157 \r
158     public Rectangle2D getBeginBounds() {\r
159         return beginBounds;\r
160     }\r
161 \r
162     public Rectangle2D getEndBounds() {\r
163         return endBounds;\r
164     }\r
165 \r
166     public double getAngle() {\r
167         return angle;\r
168     }\r
169 \r
170     public Pair<Arc2D, Path2D> getShapes() {\r
171         return shapes;\r
172     }\r
173 \r
174 \r
175 \r
176     @Override\r
177     public void render(Graphics2D g) {\r
178         if(beginBounds == null || endBounds == null) return;\r
179 \r
180         // Removed to let the global control handle rendering quality issues.\r
181         //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
182 \r
183         boolean selected = NodeUtil.isSelected(this, 2);\r
184         if(font != null) g.setFont(font);\r
185         if(selected) {\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
194         } else if (hover){\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
203         } else {\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
208         }\r
209 \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
214 \r
215                 int angle1 = 220;\r
216                 int angle2 = -40;\r
217                 if(OUTSIDE.equals(side)) {\r
218                     angle1 *= -1;\r
219                     angle2 *= -1;\r
220                 }\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
224 \r
225                 g.translate(point.getX(), point.getY());\r
226                 g.rotate(-angleRad);\r
227                 g.translate(s, c);\r
228                 g.rotate(angleRad);\r
229                 super.render(g);\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
234 \r
235     }\r
236 \r
237     boolean pressHit = false;\r
238 \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
243     }\r
244     \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
249     }\r
250 \r
251     @Override\r
252     public Rectangle2D getBoundsInLocal() {\r
253         return null;\r
254     }\r
255 \r
256     @Override\r
257     public int getEventMask() {\r
258         return super.getEventMask() | EventTypes.MouseDragBeginMask\r
259                 | EventTypes.MouseButtonPressedMask\r
260                 | EventTypes.MouseButtonReleasedMask\r
261                 ;\r
262     }\r
263     \r
264     @Override\r
265     protected boolean mouseMoved(MouseMovedEvent event) {\r
266         boolean hit = hitTest(event, HITMARGIN);\r
267         if(dragging) {\r
268             Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());\r
269             \r
270             setAngle(Arcs.angleOfArc(\r
271                     beginBounds.getCenterX(), beginBounds.getCenterY(),\r
272                     localPos.getX(), localPos.getY(),\r
273                     endBounds.getCenterX(), endBounds.getCenterY()));\r
274             repaint();\r
275         }\r
276         \r
277         if (hit != hover) {\r
278             hover = hit;\r
279             repaint();\r
280         }\r
281         return false;\r
282     }\r
283 \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
288                 return true;\r
289         } else {\r
290                 return false;\r
291         }\r
292     }\r
293     \r
294     @Override\r
295     protected boolean mouseDragged(MouseDragBegin e) {\r
296         // Get rid of dummy events from dragGestureRecognized\r
297         if (isEventDummy(e)) {\r
298                 return false;\r
299         }\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
304         if (!selected) {\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
313                                                                         continue;\r
314                                                                 }\r
315                                                                 double otherNodeDist = otherDependencyNode.getRadialDistanse(e.controlPosition);\r
316                                                                 if (Double.isNaN(otherNodeDist)) {\r
317                                                                         continue;\r
318                                                                 }\r
319                                                         if (otherDependencyNode.isDragging()) {\r
320                                                                         return true;\r
321                                                                 } \r
322                                                                 if (NodeUtil.isSelected(otherDependencyNode, 2) && (otherNodeDist < HITMARGIN)) {\r
323                                                                         return false;\r
324                                                                 } \r
325                                                                 if (otherNodeDist < myRadialDistance) {\r
326                                                                         return false;\r
327                                                                 }\r
328                                                         }\r
329                                                 }\r
330                                         }\r
331                                 }\r
332                         }\r
333                 }\r
334         }\r
335                 if ( (myRadialDistance < HITMARGIN) && !dragging) {\r
336                 dragging = true;\r
337                         return true;\r
338                 }\r
339         return false;\r
340     }\r
341 \r
342     @Override\r
343     protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {\r
344         return false;\r
345     }\r
346 \r
347     protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
348         if(dragging) {\r
349             commitProperty("angle", angle);\r
350             dragging = false;\r
351         }\r
352         return false;\r
353     }\r
354     \r
355     protected boolean isDragging() {\r
356         return dragging;\r
357     }\r
358 }\r