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