]> gerrit.simantics Code Review - simantics/sysdyn.git/blob
2bd3a51c15c0a6dd8ae32b671e0430758c7abdb4
[simantics/sysdyn.git] /
1 /*******************************************************************************\r
2  * Copyright (c) 2010, 2012, 2014 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.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
28 \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
47 \r
48 /**\r
49  * Node for dependency arrows and polarity text\r
50  * @author Teemu Lempinen\r
51  * @author Tuomas Miettinen\r
52  *\r
53  */\r
54 public class DependencyNode extends TextNode implements ISelectionPainterNode, ILoopComponentNode {\r
55 \r
56     public static final String INSIDE = "Inside";\r
57     public static final String OUTSIDE = "Outside";\r
58 \r
59     public static final double HITMARGIN = 1.7;\r
60     \r
61     private static final long serialVersionUID = 1294351381209071074L;\r
62 \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
72 \r
73     transient public boolean hover = false;\r
74     private boolean dragging = false;\r
75 \r
76     private transient PropertyChangeListener fieldListener = null;\r
77         \r
78     @Override\r
79     public void init() {\r
80         super.init();\r
81         addEventHandler(this);\r
82 \r
83     }\r
84 \r
85 \r
86     /**\r
87      * Inits the dependency node with a text\r
88      * @param text Polarity\r
89      * @param side Polarity Location\r
90      * @param font Font\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
95      */\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
98         this.side = side;\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
103     }\r
104 \r
105     @Override\r
106     public void cleanup() {\r
107         super.cleanup();\r
108     }\r
109 \r
110     public void setFieldListener(PropertyChangeListener listener) {\r
111         this.fieldListener = listener;\r
112     }\r
113 \r
114     @ServerSide\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
118         }\r
119     }\r
120 \r
121     @PropertySetter("color")\r
122     @SyncField("color")\r
123     public void setColor(Color color) {\r
124         this.color = color;\r
125     }\r
126 \r
127     @PropertySetter("stroke")\r
128     @SyncField("stroke")\r
129     public void setStroke(Stroke stroke) {\r
130         this.stroke = stroke;\r
131     }\r
132 \r
133     @PropertySetter("beginBounds")\r
134     @SyncField("beginBounds")\r
135     public void setBeginBounds(Shape beginBounds) {\r
136         this.beginBounds = beginBounds;\r
137     }\r
138 \r
139     @PropertySetter("endBounds")\r
140     @SyncField("endBounds")\r
141     public void setEndBounds(Shape endBounds) {\r
142         this.endBounds = endBounds;\r
143     }\r
144 \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
151     }\r
152 \r
153     @PropertySetter("shapes")\r
154     @SyncField("shapes")\r
155     public void setShapes(Triple<Arc2D, Path2D, Path2D> shapes) {\r
156         this.shapes = shapes;\r
157     }\r
158 \r
159     public Color getColor() {\r
160         return color;\r
161     }\r
162 \r
163     public Stroke getStroke() {\r
164         return stroke;\r
165     }\r
166 \r
167     public Shape getBeginBounds() {\r
168         return beginBounds;\r
169     }\r
170 \r
171     public Shape getEndBounds() {\r
172         return endBounds;\r
173     }\r
174 \r
175     public double getAngle() {\r
176         return angle;\r
177     }\r
178 \r
179     public Triple<Arc2D, Path2D, Path2D> getShapes() {\r
180         return shapes;\r
181     }\r
182 \r
183 \r
184 \r
185     @Override\r
186     public void render(Graphics2D g) {\r
187         if(beginBounds == null || endBounds == null) return;\r
188 \r
189         // Removed to let the global control handle rendering quality issues.\r
190         //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
191 \r
192         boolean selected = NodeUtil.isSelected(this, 2);\r
193         if(font != null) g.setFont(font);\r
194         if(selected) {\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
203             if (arrowHead) {\r
204                 g.draw(shapes.second);\r
205                 g.fill(shapes.second);\r
206             }\r
207             if (delayMark) g.draw(shapes.third);\r
208         } else if (hover){\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
217             if (arrowHead) {\r
218                 g.draw(shapes.second);\r
219                 g.fill(shapes.second);\r
220             }\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
226             if (arrowHead) {\r
227                 g.draw(shapes.second);\r
228                 g.fill(shapes.second);\r
229             }\r
230             if (delayMark) g.draw(shapes.third);\r
231         } else {\r
232             if(color != null) g.setColor(color);\r
233             if(stroke != null) g.setStroke(stroke);\r
234             g.draw(shapes.first);\r
235             if (arrowHead) {\r
236                 g.draw(shapes.second);\r
237                 g.fill(shapes.second);\r
238             }\r
239             if (delayMark) g.draw(shapes.third);\r
240         }\r
241 \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
246 \r
247                 int angle1 = 220;\r
248                 int angle2 = -40;\r
249                 if(OUTSIDE.equals(side)) {\r
250                     angle1 *= -1;\r
251                     angle2 *= -1;\r
252                 }\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
256 \r
257                 g.translate(point.getX(), point.getY());\r
258                 g.rotate(-angleRad);\r
259                 g.translate(s, c);\r
260                 g.rotate(angleRad);\r
261                 super.render(g);\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
266 \r
267     }\r
268 \r
269     boolean pressHit = false;\r
270         private HashMap<LoopNode, Boolean> loopSelectionMap = new HashMap<LoopNode, Boolean>();\r
271 \r
272         private boolean isLoopSelected() {\r
273                 return loopSelectionMap.containsValue(true);\r
274         }\r
275         \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
280     }\r
281     \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
286     }\r
287 \r
288     @Override\r
289     public Rectangle2D getBoundsInLocal() {\r
290         return null;\r
291     }\r
292 \r
293     @Override\r
294     public int getEventMask() {\r
295         return super.getEventMask() | EventTypes.MouseDragBeginMask\r
296                 | EventTypes.MouseButtonPressedMask\r
297                 | EventTypes.MouseButtonReleasedMask\r
298                 ;\r
299     }\r
300     \r
301     @Override\r
302     protected boolean mouseMoved(MouseMovedEvent event) {\r
303         boolean hit = hitTest(event, HITMARGIN);\r
304         if(dragging) {\r
305             Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());\r
306             \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
311             repaint();\r
312         }\r
313         \r
314         if (hit != hover) {\r
315             hover = hit;\r
316             repaint();\r
317         }\r
318         return false;\r
319     }\r
320 \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
325                 return true;\r
326         } else {\r
327                 return false;\r
328         }\r
329     }\r
330     \r
331     @Override\r
332     protected boolean mouseDragged(MouseDragBegin e) {\r
333         // Get rid of dummy events from dragGestureRecognized\r
334         if (isEventDummy(e)) {\r
335                 return false;\r
336         }\r
337         \r
338         // Disable dragging if LockSketch is ON\r
339                 if (SysdynElementHints.LOCK_TOOL.equals(SysdynWorkbenchUtils.getSysdynToolMode()))\r
340                         return false;\r
341                 \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
346         if (!selected) {\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
355                                                                         continue;\r
356                                                                 }\r
357                                                                 double otherNodeDist = otherDependencyNode.getRadialDistanse(e.controlPosition);\r
358                                                                 if (Double.isNaN(otherNodeDist)) {\r
359                                                                         continue;\r
360                                                                 }\r
361                                                         if (otherDependencyNode.isDragging()) {\r
362                                                                         return true;\r
363                                                                 } \r
364                                                                 if (NodeUtil.isSelected(otherDependencyNode, 2) && (otherNodeDist < HITMARGIN)) {\r
365                                                                         return false;\r
366                                                                 } \r
367                                                                 if (otherNodeDist < myRadialDistance) {\r
368                                                                         return false;\r
369                                                                 }\r
370                                                         }\r
371                                                 }\r
372                                         }\r
373                                 }\r
374                         }\r
375                 }\r
376         }\r
377                 if ( (myRadialDistance < HITMARGIN) && !dragging) {\r
378                 dragging = true;\r
379                         return true;\r
380                 }\r
381         return false;\r
382     }\r
383 \r
384     @Override\r
385     protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {\r
386         return false;\r
387     }\r
388 \r
389     protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
390         if(dragging) {\r
391             commitProperty("angle", angle);\r
392             dragging = false;\r
393         }\r
394         return false;\r
395     }\r
396     \r
397     protected boolean isDragging() {\r
398         return dragging;\r
399     }\r
400 \r
401         @Override\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
406                         repaint();\r
407                 }\r
408         }\r
409 }\r