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