]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DParentNode.java
G2DParentNode handles "undefined" child bounds separately
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / G2DParentNode.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2011 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.scenegraph.g2d;
13
14 import java.awt.Container;
15 import java.awt.Cursor;
16 import java.awt.Graphics2D;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.NoninvertibleTransformException;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.util.Arrays;
23 import java.util.Comparator;
24 import java.util.Iterator;
25
26 import org.simantics.scenegraph.INode;
27 import org.simantics.scenegraph.LoaderNode;
28 import org.simantics.scenegraph.ParentNode;
29 import org.simantics.scenegraph.ScenegraphUtils;
30 import org.simantics.scenegraph.g2d.events.Event;
31 import org.simantics.scenegraph.g2d.events.EventTypes;
32 import org.simantics.scenegraph.g2d.events.FocusEvent;
33 import org.simantics.scenegraph.g2d.events.IEventHandler;
34 import org.simantics.scenegraph.g2d.events.KeyEvent;
35 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
36 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
37 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
38 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
39 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
40 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;
41 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
42 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;
43 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
44 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
45 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseWheelMovedEvent;
46 import org.simantics.scenegraph.g2d.events.NodeEventHandler;
47 import org.simantics.scenegraph.g2d.events.TimeEvent;
48 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
49 import org.simantics.scenegraph.utils.GeometryUtils;
50 import org.simantics.scenegraph.utils.InitValueSupport;
51 import org.simantics.scenegraph.utils.NodeUtil;
52 import org.simantics.scl.runtime.function.Function1;
53 import org.simantics.scl.runtime.function.Function2;
54 import org.simantics.utils.threads.AWTThread;
55
56 /**
57  * @author Tuukka Lehtonen
58  */
59 public class G2DParentNode extends ParentNode<IG2DNode> implements IG2DNode, InitValueSupport, LoaderNode {
60
61     private static final long             serialVersionUID  = 4966823616578337420L;
62
63     protected static final IG2DNode[]     EMPTY_NODE_ARRAY  = {};
64     protected static final String[]       EMPTY_STRING_ARRAY  = {};
65
66     private transient volatile String[]   sortedChildrenIds = null;
67     private transient volatile IG2DNode[] sortedChildren = null;
68
69     protected AffineTransform             transform         = IdentityAffineTransform.INSTANCE;
70
71     /**
72      * Z-index of this node. Default value is 0.
73      */
74     protected int z = 0;
75
76     public void invalidateChildOrder() {
77         sortedChildrenIds = null;
78         sortedChildren = null;
79     }
80
81     @Override
82     protected void childrenChanged() {
83         invalidateChildOrder();
84     }
85
86     @Override
87     @SyncField("z")
88     public void setZIndex(int z) {
89         if (z != this.z) {
90             G2DParentNode parent = (G2DParentNode) getParent();
91             if (parent != null)
92                 parent.invalidateChildOrder();
93             this.z = z;
94         }
95     }
96
97     @Override
98     public int getZIndex() {
99         return z;
100     }
101
102     @Override
103     public boolean validate() {
104         return true;
105     }
106
107     @Override
108     public void render(Graphics2D g2d) {
109         AffineTransform ot = null;
110         if (!transform.isIdentity()) {
111             ot = g2d.getTransform();
112             g2d.transform(transform);
113         }
114
115         for (IG2DNode node : getSortedNodes()) {
116             if (node.validate()) {
117                 node.render(g2d);
118             }
119         }
120
121         if (ot != null)
122             g2d.setTransform(ot);
123     }
124
125     @Override
126     public void accept(IG2DNodeVisitor visitor) {
127         visitor.enter(this);
128         for (IG2DNode node : getSortedNodes()) {
129             if (node.validate()) {
130                 node.accept(visitor);
131             }
132         }
133         visitor.leave(this);
134     }
135
136     /**
137      * Return the IDs of the children of this node in ascending Z order. This
138      * method will always allocate a new result list and sort it. To get the IDs
139      * of the child nodes you need to use this method. Otherwise use
140      * {@link #getSortedNodes()} instead, it is faster more memory efficient.
141      * 
142      * @return child node IDs in ascending Z order
143      */
144     public String[] getSortedNodesById() {
145         if (sortedChildrenIds != null)
146             return sortedChildrenIds;
147         if (children.isEmpty())
148             return EMPTY_STRING_ARRAY;
149
150         String[] sorted = null;
151         synchronized (children) {
152             if (sortedChildrenIds != null)
153                 return sortedChildrenIds;
154             sorted = children.keySet().toArray(EMPTY_STRING_ARRAY);
155             Arrays.sort(sorted, new Comparator<String>() {
156                 @Override
157                 public int compare(String a, String b) {
158                     int za = getNode(a).getZIndex();
159                     int zb = getNode(b).getZIndex();
160                     return za < zb ? -1 : (za == zb ? 0 : 1);
161                 }
162             });
163             sortedChildrenIds = sorted;
164         }
165         return sorted;
166     }
167
168     public static final Comparator<IG2DNode> G2DNODE_Z_COMPARATOR = new Comparator<IG2DNode>() {
169         @Override
170         public int compare(IG2DNode a, IG2DNode b) {
171             int za = a.getZIndex();
172             int zb = b.getZIndex();
173             return za < zb ? -1 : (za == zb ? 0 : 1);
174         }
175     };
176
177     /**
178      * @return child nodes in ascending Z order
179      */
180     public IG2DNode[] getSortedNodes() {
181         if (sortedChildren != null)
182             return sortedChildren;
183         if (children.isEmpty())
184             return EMPTY_NODE_ARRAY;
185
186         IG2DNode[] sorted = null;
187         synchronized (children) {
188             if (sortedChildren != null)
189                 return sortedChildren;
190             sorted = children.values().toArray(EMPTY_NODE_ARRAY);
191             Arrays.sort(sorted, G2DNODE_Z_COMPARATOR);
192             sortedChildren = sorted;
193         }
194         return sorted;
195     }
196
197     @Override
198     public void cleanup() {
199         rootNodeCache = DISPOSED;
200         sortedChildren = null;
201         sortedChildrenIds = null;
202         transform = IdentityAffineTransform.INSTANCE;
203         super.cleanup();
204     }
205
206     @Override
207     public void repaint() {
208         INode parent = getParent();
209         while(parent != null && !(parent instanceof G2DSceneGraph))
210             parent = parent.getParent();
211         if(parent == null || ((G2DSceneGraph)parent).getRootPane() == null) return;
212         ((G2DSceneGraph)parent).getRootPane().repaint();
213     }
214
215     @Override
216     public void asyncRemoveNode(INode node) {
217         ParentNode<?> parent = getParent();
218         while(parent != null && parent.getParent() != null)
219             parent = parent.getParent();
220
221         if(parent != null) {
222             parent.asyncRemoveNode(node); // Pass to root element
223         } else {
224             // This is root, should do something... (Actually G2DSceneGraph does something)
225         }
226     }
227
228     // Bounds and transformation
229
230     @Override
231     public AffineTransform getTransform() {
232         return transform;
233     }
234
235     @Override
236     @PropertySetter("Transform")
237     @SyncField("transform")
238     public void setTransform(AffineTransform transform) {
239         assert(transform != null);
240         if (transform.isIdentity())
241             this.transform = IdentityAffineTransform.INSTANCE;
242         else
243             this.transform = transform;
244     }
245
246     /**
247      * Return bounds transformed with local transformation
248      * 
249      * @return transformed bounds
250      */
251     @Override
252     public Rectangle2D getBounds() {
253         Rectangle2D local = getBoundsInLocal();
254         if (local == null)
255             return null;
256         // Optimize trivial identity transform case.
257         if (transform.isIdentity())
258             return local;
259         return transform.createTransformedShape(local).getBounds2D();
260     }
261
262     // Helper methods for bounds checking
263
264     @Override
265     public boolean contains(Point2D point) {
266         Rectangle2D bounds = getBounds();
267         if(bounds == null) return false;
268         return bounds.contains(point);
269     }
270
271     @Override
272     public boolean intersects(Rectangle2D b) {
273         if (b == null)
274             return true;
275         Rectangle2D a = getBounds();
276         if (a == null)
277             return true;
278         /*
279          * Compared to Rectangle2D.intersects, this
280          * intersects closed (not open) shapes.
281          */
282         double ax = a.getX();
283         double ay = a.getY();
284         double aw = a.getWidth();
285         double ah = a.getHeight();
286         double bx = b.getX();
287         double by = b.getY();
288         double bw = b.getWidth();
289         double bh = b.getHeight();
290         return (ax + aw >= bx &&
291                 ay + ah >= by &&
292                 ax <= bx + bw &&
293                 ay <= by + bh);
294     }
295
296     @Override
297     public Point2D localToParent(Point2D point) {
298         return transform.transform(point, null);
299     }
300
301     @Override
302     public Rectangle2D localToParent(Rectangle2D rect) {
303         return transform.createTransformedShape(rect).getBounds2D();
304     }
305
306     @Override
307     public Point2D parentToLocal(Point2D point) {
308         AffineTransform inverse = null;
309         try {
310             inverse = transform.createInverse();
311             return inverse.transform(point, null);
312         } catch (NoninvertibleTransformException e) {
313             e.printStackTrace(); // FIXME
314         }
315         return point;
316     }
317
318     @Override
319     public Rectangle2D parentToLocal(Rectangle2D rect) {
320         AffineTransform inverse = null;
321         try {
322             inverse = transform.createInverse();
323             return inverse.createTransformedShape(rect).getBounds2D();
324         } catch (NoninvertibleTransformException e) {
325             e.printStackTrace(); // FIXME
326         }
327         return rect;
328     }
329
330     @Override
331     public Rectangle2D getBoundsInLocal() {
332         return getBoundsInLocal(false);
333     }
334
335     @Override
336     public Rectangle2D getBoundsInLocal(boolean ignoreNulls) {
337         Iterator<IG2DNode> it = getNodes().iterator();
338         if(!it.hasNext())
339             return null;
340         Rectangle2D bounds = null;
341         while(it.hasNext()) {
342             IG2DNode node = it.next();
343             Rectangle2D b = node.getBounds();
344             if(b == null && !ignoreNulls)
345                 return null;
346             if(b != null) {
347                 if(!GeometryUtils.isUndefinedRectangle(b)) {
348                     if(bounds == null) {
349                         bounds = b.getFrame();
350                     } else {
351                         bounds.add(b);
352                     }
353                 }
354             }
355         }
356         return bounds;
357     }
358
359     @Override
360     public Point2D localToControl(Point2D point) {
361         IG2DNode node = this;
362         while(node != null) {
363             node.getTransform().transform(point, null);
364             node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure
365         }
366         return point;
367     }
368
369     @Override
370     public Rectangle2D localToControl(Rectangle2D rect) {
371         Shape shape = rect;
372         IG2DNode node = this;
373         while(node != null) {
374             shape = node.getTransform().createTransformedShape(shape);
375             node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure
376         }
377
378         return shape.getBounds2D();
379     }
380
381     public Point2D controlToLocal(Point2D point) {
382         AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
383         if (at == null)
384             return point;
385         return at.transform(point, null);
386     }
387
388     public Rectangle2D controlToLocal(Rectangle2D rect) {
389         AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
390         if (at == null)
391             return rect;
392         return GeometryUtils.transformRectangle(at, rect);
393     }
394
395     /**
396      * Damn slow method for picking node
397      * 
398      * @param point
399      * @return
400      */
401     public IG2DNode pickNode(Point2D point) {
402         Point2D localpoint = parentToLocal(point);
403         IG2DNode[] nodes = getSortedNodes();
404
405         for(int i = nodes.length-1; i >= 0; i--) {
406             IG2DNode n = nodes[i]; // Reverse order..
407             if(n instanceof G2DParentNode) {
408                 IG2DNode node = ((G2DParentNode)n).pickNode(localpoint);
409                 if(node != null)
410                     return node;
411             } else if(n.contains(localpoint)) {
412                 return n;
413             }
414         }
415         return null;
416     }
417
418     @Override
419     public String toString() {
420         return super.toString() + " [z=" + z + ", transform=" + transform + "]";
421     }
422
423     /**
424      * TODO: not sure if this is a good idea at all.
425      * 
426      * @see org.simantics.scenegraph.utils.InitValueSupport#initValues()
427      */
428     @Override
429     public void initValues() {
430         for (IG2DNode node : getSortedNodes()) {
431             if (node instanceof InitValueSupport) {
432                 ((InitValueSupport) node).initValues();
433             }
434         }
435     }
436
437     /**
438      * @see org.simantics.scenegraph.g2d.IG2DNode#getRootNode()
439      */
440     public G2DSceneGraph getRootNode2D() {
441         ParentNode<?> root = getRootNode();
442         return (G2DSceneGraph) root;
443     }
444
445     @Override
446     public boolean hasFocus() {
447         return getFocusNode() == this;
448     }
449
450     @Override
451     public IG2DNode getFocusNode() {
452         return getRootNode2D().getFocusNode();
453     }
454
455     @Override
456     public void setFocusNode(IG2DNode node) {
457         getRootNode2D().setFocusNode(node);
458     }
459
460     protected NodeEventHandler getEventHandler() {
461         return NodeUtil.getNodeEventHandler(this);
462     }
463
464     protected void addEventHandler(IEventHandler handler) {
465         getEventHandler().add(handler);
466     }
467
468     protected void removeEventHandler(IEventHandler handler) {
469         getEventHandler().remove(handler);
470     }
471
472     @Override
473     public int getEventMask() {
474         return 0;
475     }
476
477     @Override
478     public boolean handleEvent(Event e) {
479         int eventType = EventTypes.toType(e);
480         switch (eventType) {
481             case EventTypes.Command:
482                 return handleCommand((CommandEvent) e);
483
484             case EventTypes.FocusGained:
485             case EventTypes.FocusLost:
486                 return handleFocusEvent((FocusEvent) e);
487
488             case EventTypes.KeyPressed:
489                 return keyPressed((KeyPressedEvent) e);
490             case EventTypes.KeyReleased:
491                 return keyReleased((KeyReleasedEvent) e);
492
493             case EventTypes.MouseButtonPressed:
494                 return mouseButtonPressed((MouseButtonPressedEvent) e);
495             case EventTypes.MouseButtonReleased:
496                 return mouseButtonReleased((MouseButtonReleasedEvent) e);
497             case EventTypes.MouseClick:
498                 return mouseClicked((MouseClickEvent) e);
499             case EventTypes.MouseDoubleClick:
500                 return mouseDoubleClicked((MouseDoubleClickedEvent) e);
501             case EventTypes.MouseMoved:
502                 return mouseMoved((MouseMovedEvent) e);
503             case EventTypes.MouseDragBegin:
504                 return mouseDragged((MouseDragBegin) e);
505             case EventTypes.MouseEnter:
506                 return mouseEntered((MouseEnterEvent) e);
507             case EventTypes.MouseExit:
508                 return mouseExited((MouseExitEvent) e);
509             case EventTypes.MouseWheel:
510                 return mouseWheelMoved((MouseWheelMovedEvent) e);
511
512             case EventTypes.Time:
513                 return handleTimeEvent((TimeEvent) e);
514         }
515         return false;
516     }
517
518     protected boolean keyReleased(KeyReleasedEvent e) {
519         return false;
520     }
521
522     protected boolean keyPressed(KeyPressedEvent e) {
523         return false;
524     }
525
526     protected boolean handleCommand(CommandEvent e) {
527         return false;
528     }
529
530     protected boolean handleFocusEvent(FocusEvent e) {
531         return false;
532     }
533
534     protected boolean handleKeyEvent(KeyEvent e) {
535         return false;
536     }
537
538     protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
539         return false;
540     }
541
542     protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
543         return false;
544     }
545
546     protected boolean mouseClicked(MouseClickEvent e) {
547         return false;
548     }
549
550     protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {
551         return false;
552     }
553
554     protected boolean mouseMoved(MouseMovedEvent e) {
555         return false;
556     }
557
558     protected boolean mouseDragged(MouseDragBegin e) {
559         return false;
560     }
561
562     protected boolean mouseEntered(MouseEnterEvent e) {
563         return false;
564     }
565
566     protected boolean mouseExited(MouseExitEvent e) {
567         return false;
568     }
569
570     protected boolean mouseWheelMoved(MouseWheelMovedEvent e) {
571         return false;
572     }
573
574     protected boolean handleTimeEvent(TimeEvent e) {
575         return false;
576     }
577
578     protected void setCursor(int cursorType) {
579         Container rootPane = NodeUtil.findRootPane(this);
580         if (rootPane != null)
581             rootPane.setCursor(Cursor.getPredefinedCursor(cursorType));
582     }
583
584     protected void setCursor(Cursor cursor) {
585         Container rootPane = NodeUtil.findRootPane(this);
586         if (rootPane != null)
587             rootPane.setCursor(cursor);
588     }
589
590         @Override
591         public Function1<Object, Boolean> getPropertyFunction(String propertyName) {
592                 return ScenegraphUtils.getMethodPropertyFunction(AWTThread.getThreadAccess(), this, propertyName);
593         }
594         
595         @Override
596         public <T> T getProperty(String propertyName) {
597                 return null;
598         }
599         
600         @Override
601         public void setPropertyCallback(Function2<String, Object, Boolean> callback) {
602         }
603     
604         public void synchronizeTransform(double[] data) {
605                 this.setTransform(new AffineTransform(data));
606         }
607 }