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