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