]> gerrit.simantics Code Review - simantics/sysdyn.git/blob
e9fc9702c1b8967c4f957078d34936f6d578a466
[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.Color;\r
15 import java.awt.Composite;\r
16 import java.awt.Font;\r
17 import java.awt.Shape;\r
18 import java.awt.geom.AffineTransform;\r
19 import java.awt.geom.Area;\r
20 import java.awt.geom.Rectangle2D;\r
21 import java.util.ArrayList;\r
22 import java.util.Collection;\r
23 import java.util.Collections;\r
24 import java.util.HashSet;\r
25 import java.util.List;\r
26 import java.util.Map;\r
27 import java.util.Set;\r
28 \r
29 import org.simantics.db.Resource;\r
30 import org.simantics.g2d.connection.ConnectionEntity;\r
31 import org.simantics.g2d.connection.ConnectionEntity.ConnectionEvent;\r
32 import org.simantics.g2d.connection.ConnectionEntity.ConnectionListener;\r
33 import org.simantics.g2d.connection.handler.ConnectionHandler;\r
34 import org.simantics.g2d.diagram.DiagramHints;\r
35 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;\r
36 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
37 import org.simantics.g2d.element.ElementClass;\r
38 import org.simantics.g2d.element.ElementHints;\r
39 import org.simantics.g2d.element.ElementUtils;\r
40 import org.simantics.g2d.element.IElement;\r
41 import org.simantics.g2d.element.handler.Children;\r
42 import org.simantics.g2d.element.handler.InternalSize;\r
43 import org.simantics.g2d.element.handler.Outline;\r
44 import org.simantics.g2d.element.handler.Pick;\r
45 import org.simantics.g2d.element.handler.Pick2;\r
46 import org.simantics.g2d.element.handler.SceneGraph;\r
47 import org.simantics.g2d.element.handler.SelectionOutline;\r
48 import org.simantics.g2d.element.handler.Transform;\r
49 import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;\r
50 import org.simantics.g2d.element.handler.impl.ParentImpl;\r
51 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;\r
52 import org.simantics.g2d.element.handler.impl.TextColorImpl;\r
53 import org.simantics.g2d.element.handler.impl.TextFontImpl;\r
54 import org.simantics.g2d.element.handler.impl.TextImpl;\r
55 import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;\r
56 import org.simantics.g2d.utils.GeometryUtils;\r
57 import org.simantics.scenegraph.g2d.G2DParentNode;\r
58 import org.simantics.scenegraph.g2d.IG2DNode;\r
59 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
60 import org.simantics.utils.datastructures.ListenerList;\r
61 import org.simantics.utils.datastructures.Pair;\r
62 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
63 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
64 \r
65 /**\r
66  * An element class for single connection entity elements. A sysdyn connection\r
67  * entity consists of a single edge. Sysdyn connections can't be branched.\r
68  * \r
69  * @author Tuukka Lehtonen\r
70  */\r
71 public class SysdynConnectionClass {\r
72 \r
73     public static final ElementClass CLASS =\r
74         ElementClass.compile(\r
75                 TextImpl.INSTANCE,\r
76                 TextFontImpl.DEFAULT,\r
77                 TextColorImpl.BLACK,\r
78                 FixedTransform.INSTANCE,\r
79                 ConnectionPick.INSTANCE,\r
80                 ConnectionBounds.INSTANCE,\r
81                 ConnectionSelectionOutline.INSTANCE,\r
82                 ConnectionHandlerImpl.INSTANCE,\r
83                 ConnectionChildren.INSTANCE,\r
84                 ParentImpl.INSTANCE,\r
85                 ConnectionSceneGraph.INSTANCE,\r
86                 SimpleElementLayers.INSTANCE\r
87         ).setId(SysdynConnectionClass.class.getSimpleName());\r
88 \r
89     private static class ThreadLocalList extends ThreadLocal<List<IElement>> {\r
90         @Override\r
91         protected java.util.List<IElement> initialValue() {\r
92             return new ArrayList<IElement>();\r
93         }\r
94     };\r
95 \r
96     private static final ThreadLocal<List<IElement>> perThreadSceneGraphList = new ThreadLocalList();\r
97     private static final ThreadLocal<List<IElement>> perThreadBoundsList = new ThreadLocalList();\r
98     private static final ThreadLocal<List<IElement>> perThreadShapeList = new ThreadLocalList();\r
99     private static final ThreadLocal<List<IElement>> perThreadPickList = new ThreadLocalList();\r
100 \r
101     static class ConnectionHandlerImpl implements ConnectionHandler {\r
102 \r
103         public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl();\r
104 \r
105         private static final long serialVersionUID = 3267139233182458330L;\r
106 \r
107         @Override\r
108         public Collection<IElement> getBranchPoints(IElement connection, Collection<IElement> result) {\r
109             ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
110             if (entity == null)\r
111                 return Collections.emptySet();\r
112             return entity.getBranchPoints(result);\r
113         }\r
114 \r
115         @Override\r
116         public Collection<IElement> getChildren(IElement connection, Collection<IElement> result) {\r
117             ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
118             if (entity == null)\r
119                 return Collections.emptySet();\r
120             result = entity.getSegments(result);\r
121             return entity.getBranchPoints(result);\r
122         }\r
123 \r
124         @Override\r
125         public Collection<IElement> getSegments(IElement connection, Collection<IElement> result) {\r
126             ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
127             if (entity == null)\r
128                 return Collections.emptySet();\r
129             return entity.getSegments(result);\r
130         }\r
131 \r
132         @Override\r
133         public Collection<Connection> getTerminalConnections(IElement connection, Collection<Connection> result) {\r
134             ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
135             if (entity == null)\r
136                 return Collections.emptySet();\r
137             return entity.getTerminalConnections(result);\r
138         }\r
139     }\r
140 \r
141     static final class ConnectionSceneGraph implements SceneGraph {\r
142 \r
143         public static final ConnectionSceneGraph INSTANCE = new ConnectionSceneGraph();\r
144 \r
145         private static final long serialVersionUID = 4232871859964883266L;\r
146 \r
147         @Override\r
148         public void init(IElement connection, G2DParentNode parent) {\r
149             ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
150             if (ce == null)\r
151                 return;\r
152 \r
153             // Painting is single-threaded, it is OK to use a single thread-local collection here.\r
154             List<IElement> children = perThreadSceneGraphList.get();\r
155             children.clear();\r
156             ce.getSegments(children);\r
157             ce.getBranchPoints(children);\r
158             //new Exception("painting connection entity " + ce.hashCode() + " with " + children.size() + " segments and branch points").printStackTrace();\r
159             if (children.isEmpty())\r
160                 return;\r
161 \r
162             Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();\r
163 \r
164                 Map<String, Pair<Resource, Object>> properties = connection.getHint(DiagramHints.PROPERTIES);\r
165                 \r
166                 Font font = connection.getHint(ElementHints.KEY_FONT);\r
167                 Color color = connection.getHint(ElementHints.KEY_TEXT_COLOR);\r
168             \r
169             int zIndex = 0;\r
170             for (IElement child : children) {\r
171                 \r
172                 ElementClass ec = child.getElementClass();\r
173 \r
174                 Transform transform = child.getElementClass().getSingleItem(Transform.class);\r
175                 assert (transform != null);\r
176                 AffineTransform at2 = transform.getTransform(child);\r
177                 if (at2 == null)\r
178                     continue;\r
179 \r
180                 if(properties != null)\r
181                         child.setHint(DiagramHints.PROPERTIES, properties);\r
182                 \r
183                 if(font != null)\r
184                     child.setHint(ElementHints.KEY_FONT, font);\r
185                 \r
186                 if(color != null)\r
187                     child.setHint(ElementHints.KEY_TEXT_COLOR, color);\r
188                 \r
189                 SingleElementNode holder = child.getHint(ElementHints.KEY_SG_NODE);\r
190                 if (holder == null) {\r
191                     holder = parent.addNode(ElementUtils.generateNodeId(child), SingleElementNode.class);\r
192                     child.setHint(ElementHints.KEY_SG_NODE, holder);\r
193                 }\r
194                 holder.setZIndex(++zIndex);\r
195 \r
196                 Composite composite = child.getHint(ElementHints.KEY_COMPOSITE);\r
197 \r
198                 holder.setTransform((AffineTransform) at2.clone());\r
199                 holder.setComposite(composite);\r
200                 holder.setVisible(true);\r
201 \r
202                 // New node handler\r
203                 for (SceneGraph n : ec.getItemsByClass(SceneGraph.class)) {\r
204                     n.init(child, holder);\r
205                 }\r
206                 tmp.add(holder);\r
207             }\r
208 \r
209             // Hide unaccessed nodes (but don't remove)\r
210             for (IG2DNode node : parent.getNodes()) {\r
211                 if (node instanceof SingleElementNode) {\r
212                     if (!tmp.contains(node)) {\r
213                         ((SingleElementNode)node).setVisible(false);\r
214                     }\r
215                 } else {\r
216                     //System.out.println("WHAT IS THIS: ");\r
217                     //NodeDebug.printSceneGraph(((Node) node));\r
218                 }\r
219             }\r
220 \r
221             // Don't leave dangling references behind.\r
222             children.clear();\r
223         }\r
224 \r
225         @Override\r
226         public void cleanup(IElement e) {\r
227         }\r
228     }\r
229 \r
230     static final class ConnectionBounds implements InternalSize, Outline {\r
231 \r
232         public static final ConnectionBounds INSTANCE = new ConnectionBounds();\r
233 \r
234         private static final long serialVersionUID = 4232871859964883266L;\r
235 \r
236         @Override\r
237         public Rectangle2D getBounds(IElement e, Rectangle2D size) {\r
238             ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
239             if (ce == null)\r
240                 return size;\r
241 \r
242             Collection<IElement> parts = perThreadBoundsList.get();\r
243             parts.clear();\r
244             parts = ce.getSegments(parts);\r
245             if (parts.isEmpty())\r
246                 return size;\r
247 \r
248             parts = ce.getBranchPoints(parts);\r
249 \r
250             Rectangle2D temp = null;\r
251             for (IElement part : parts) {\r
252                 if (ElementUtils.isHidden(part))\r
253                     continue;\r
254 \r
255                 // Using on-diagram coordinates because neither connections nor\r
256                 // edges have a non-identity transform which means that\r
257                 // coordinates are always absolute. Therefore branch point\r
258                 // bounds also need to be calculated in absolute coordinates.\r
259                 Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(part, size);\r
260                 if (bounds == null)\r
261                     continue;\r
262 \r
263 //                System.out.println("InternalSize BOUNDS: " + size + " for part " + part + " " + part.getElementClass());\r
264                 if (temp == null) {\r
265                     temp = new Rectangle2D.Double();\r
266                     temp.setRect(bounds);\r
267                 } else\r
268                     Rectangle2D.union(temp, bounds, temp);\r
269                 //System.out.println("InternalSize Combined BOUNDS: " + temp);\r
270             }\r
271             if (temp != null) {\r
272                 if (size == null)\r
273                     size = temp;\r
274                 else\r
275                     size.setRect(temp);\r
276             }\r
277 \r
278             // Don't leave dangling references behind.\r
279             parts.clear();\r
280 \r
281             return size;\r
282         }\r
283 \r
284         private Shape getSelectionShape(IElement forPart) {\r
285             for (SelectionOutline so : forPart.getElementClass().getItemsByClass(SelectionOutline.class)) {\r
286                 Shape shape = so.getSelectionShape(forPart);\r
287                 if (shape != null)\r
288                     return shape;\r
289             }\r
290             // Using on-diagram coordinates because neither connections nor\r
291             // edges have a non-identity transform which means that\r
292             // coordinates are always absolute. Therefore branch point\r
293             // shape also needs to be calculated in absolute coordinates.\r
294             Shape shape = ElementUtils.getElementShapeOrBoundsOnDiagram(forPart);\r
295             return shape;\r
296             //return shape.getBounds2D();\r
297         }\r
298 \r
299         @Override\r
300         public Shape getElementShape(IElement e) {\r
301             ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
302             if (ce == null)\r
303                 return new Rectangle2D.Double();\r
304 \r
305             Collection<IElement> parts = perThreadShapeList.get();\r
306             parts.clear();\r
307             parts = ce.getSegments(parts);\r
308             if (parts.isEmpty())\r
309                 return new Rectangle2D.Double();\r
310             parts = ce.getBranchPoints(parts);\r
311 \r
312             if (parts.size() == 1) {\r
313                 Shape shape = getSelectionShape(parts.iterator().next());\r
314                 //System.out.println("Outline SHAPE: " + shape);\r
315                 //System.out.println("Outline BOUNDS: " + shape.getBounds2D());\r
316                 return shape;\r
317             }\r
318 \r
319             //System.out.println("Outline: " + e);\r
320             Area area = new Area();\r
321             for (IElement part : parts) {\r
322                 //System.out.println(part);\r
323 \r
324                 Shape shape = getSelectionShape(part);\r
325 \r
326                 Rectangle2D bounds = shape.getBounds2D();\r
327 //                System.out.println("    shape: " + shape);\r
328 //                System.out.println("    bounds: " + bounds);\r
329 \r
330                 if (bounds.isEmpty()) {\r
331                     double w = bounds.getWidth();\r
332                     double h = bounds.getHeight();\r
333                     if (w <= 0.0 && h <= 0.0)\r
334                         continue;\r
335 \r
336                     // Need to expand shape in either width or height to make it visible.\r
337                     final double exp = 0.1;\r
338                     if (w <= 0.0)\r
339                         shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, 0, 0, exp, exp);\r
340                     else if (h <= 0.0)\r
341                         shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, exp, exp, 0, 0);\r
342                 }\r
343 \r
344                 //System.out.println("    final shape: " + shape);\r
345                 //shape =  bounds;\r
346 \r
347                 Area a = null;\r
348                 if (shape instanceof Area)\r
349                     a = (Area) shape;\r
350                 else\r
351                     a = new Area(shape);\r
352                 area.add(a);\r
353             }\r
354 \r
355             // Don't leave dangling references behind.\r
356             parts.clear();\r
357 \r
358             //System.out.println("    connection area outline: " + area);\r
359             //System.out.println("    connection area outline bounds: " + area.getBounds2D());\r
360             return area;\r
361         }\r
362     }\r
363 \r
364     public static class ConnectionPick implements Pick2 {\r
365 \r
366         public final static ConnectionPick INSTANCE = new ConnectionPick();\r
367 \r
368         private static final long serialVersionUID = 1L;\r
369 \r
370         @Override\r
371         public boolean pickTest(IElement e, Shape s, PickPolicy policy) {\r
372             ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
373             if (ce == null)\r
374                 return false;\r
375 \r
376             // Primarily pick branch points and then edges.\r
377             Collection<IElement> parts = perThreadPickList.get();\r
378             parts.clear();\r
379             parts = ce.getBranchPoints(parts);\r
380             parts = ce.getSegments(parts);\r
381             if (parts.isEmpty())\r
382                 return false;\r
383 \r
384             for (IElement part : parts) {\r
385                 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {\r
386 //                    System.out.println("TESTING: " + part + " : " + s + " : " + policy);\r
387                     if (pick.pickTest(part, s, policy)) {\r
388                         //System.out.println("  HIT!");\r
389                         return true;\r
390                     }\r
391                 }\r
392             }\r
393 \r
394             parts.clear();\r
395 \r
396             return false;\r
397         }\r
398 \r
399         @Override\r
400         public int pick(IElement e, Shape s, PickPolicy policy, Collection<IElement> result) {\r
401             int oldResultSize = result.size();\r
402 \r
403 //            new Exception("SysdynConnectionClass.pick: " + e + " : " + s + " : " + policy).printStackTrace();\r
404             \r
405             ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
406             if (ce == null)\r
407                 return 0;\r
408 \r
409             // Primarily pick branch points and then edges.\r
410             List<IElement> parts = perThreadPickList.get();\r
411             parts.clear();\r
412 \r
413             ce.getSegments(parts);\r
414             int edges = parts.size();\r
415             ce.getBranchPoints(parts);\r
416             int branchPoints = parts.size() - edges;\r
417 \r
418             boolean singleEdge = branchPoints == 0 && edges == 1;\r
419 \r
420             if (parts.isEmpty())\r
421                 return 0;\r
422 \r
423             // See whether the whole connection is to be picked..\r
424             boolean pickConnection = false;\r
425             wholeConnectionPick:\r
426                 for (Outline outline : e.getElementClass().getItemsByClass(Outline.class)) {\r
427                     Shape elementShape = outline.getElementShape(e);\r
428                     if (elementShape == null)\r
429                         continue;\r
430 \r
431                     switch (policy) {\r
432                         case PICK_CONTAINED_OBJECTS:\r
433                             if (GeometryUtils.contains(s, elementShape)) {\r
434                                 pickConnection = true;\r
435                                 break wholeConnectionPick;\r
436                             }\r
437                             break;\r
438                         case PICK_INTERSECTING_OBJECTS:\r
439                             if (GeometryUtils.intersects(s, elementShape)) {\r
440                                 pickConnection = true;\r
441                                 break wholeConnectionPick;\r
442                             }\r
443                             break;\r
444                     }\r
445                 }\r
446 \r
447             ArrayList<IElement> picks = null;\r
448 \r
449             // Pick connection segments\r
450             for (int i = 0; i < edges; ++i) {\r
451                 IElement part = parts.get(i);\r
452                 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {\r
453 //                    System.out.println("TESTING SEGMENT: " + part + " : " + s + " : " + policy);\r
454                     if (pick.pickTest(part, s, policy)) {\r
455 //                        System.out.println("  HIT!");\r
456                         if (picks == null)\r
457                             picks = new ArrayList<IElement>(4);\r
458                         picks.add(e);\r
459                         break;\r
460                     }\r
461                 }\r
462             }\r
463 \r
464             // Pick the whole connection ?\r
465             if (pickConnection) {\r
466                 if (picks == null)\r
467                     picks = new ArrayList<IElement>(4);\r
468                 picks.add(e);\r
469             }\r
470 \r
471             // Pick branch/route points\r
472             for (int i = edges; i < parts.size(); ++i) {\r
473                 IElement part = parts.get(i);\r
474                 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {\r
475                     //System.out.println("TESTING BRANCHPOINT: " + part + " : " + s + " : " + policy);\r
476                     if (pick.pickTest(part, s, policy)) {\r
477                         //System.out.println("  HIT!");\r
478                         if (picks == null)\r
479                             picks = new ArrayList<IElement>(4);\r
480                         picks.add(part);\r
481                         break;\r
482                     }\r
483                 }\r
484             }\r
485 \r
486             if (picks != null) {\r
487                 // Add the discovered pickable children to the result after the\r
488                 // parent to make the parent the primary pickable.\r
489                 // Skip the children if there is only one child.\r
490                 if (!singleEdge) {\r
491                     result.addAll(picks);\r
492                 } else {\r
493                     result.add(e);\r
494                 }\r
495             }\r
496 \r
497             parts.clear();\r
498 \r
499 //            System.out.println("pick result size = " + result.size());\r
500 \r
501             return result.size() - oldResultSize;\r
502         }\r
503     }\r
504 \r
505     private static final Key CHILD_LISTENERS = new KeyOf(ListenerList.class, "CHILD_LISTENERS");\r
506 \r
507     public static class ConnectionChildren implements Children, ConnectionListener {\r
508 \r
509         public final static ConnectionChildren INSTANCE = new ConnectionChildren();\r
510 \r
511         private static final long serialVersionUID = 1L;\r
512 \r
513         @Override\r
514         public Collection<IElement> getChildren(IElement element, Collection<IElement> result) {\r
515             ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
516             if (ce == null) {\r
517                 if (result == null)\r
518                     result = new ArrayList<IElement>(0);\r
519                 return result;\r
520             }\r
521             result = ce.getSegments(result);\r
522             result = ce.getBranchPoints(result);\r
523             return result;\r
524         }\r
525 \r
526         @Override\r
527         public void addChildListener(IElement element, ChildListener listener) {\r
528             ListenerList<ChildListener> ll = null;\r
529             synchronized (element) {\r
530                 ll = element.getHint(CHILD_LISTENERS);\r
531                 if (ll == null) {\r
532                     ll = new ListenerList<ChildListener>(ChildListener.class);\r
533                     element.setHint(CHILD_LISTENERS, ll);\r
534                     ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
535                     entity.setListener(this);\r
536                 }\r
537             }\r
538             ll.add(listener);\r
539         }\r
540 \r
541         @Override\r
542         public void removeChildListener(IElement element, ChildListener listener) {\r
543             synchronized (element) {\r
544                 ListenerList<ChildListener> ll = element.getHint(CHILD_LISTENERS);\r
545                 if (ll == null)\r
546                     return;\r
547                 ll.remove(listener);\r
548                 if (ll.isEmpty()) {\r
549                     ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
550                     entity.setListener(null);\r
551                 }\r
552             }\r
553         }\r
554 \r
555         @Override\r
556         public void connectionChanged(ConnectionEvent event) {\r
557             fireChildrenChanged(event);\r
558         }\r
559 \r
560         private void fireChildrenChanged(ConnectionEvent event) {\r
561             ListenerList<ChildListener> ll = event.connection.getHint(CHILD_LISTENERS);\r
562             if (ll == null)\r
563                 return;\r
564             ChildEvent ce = new ChildEvent(event.connection, event.removedParts, event.addedParts);\r
565             for (ChildListener cl : ll.getListeners()) {\r
566                 cl.elementChildrenChanged(ce);\r
567             }\r
568         }\r
569 \r
570     }\r
571 \r
572 }\r