]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DParentNode.java
af8c5630dc84c88c46c350b07c540ee9926e9ae6
[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.getBounds();
353             if(b == null && !ignoreNulls)
354                 return null;
355             if(b != null) {
356                 if(!GeometryUtils.isUndefinedRectangle(b)) {
357                     if(bounds == null) {
358                         bounds = b.getFrame();
359                     } else {
360                         bounds.add(b);
361                     }
362                 }
363             }
364         }
365         return bounds;
366     }
367
368     @Override
369     public Point2D localToControl(Point2D point) {
370         IG2DNode node = this;
371         while(node != null) {
372             node.getTransform().transform(point, null);
373             node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure
374         }
375         return point;
376     }
377
378     @Override
379     public Rectangle2D localToControl(Rectangle2D rect) {
380         Shape shape = rect;
381         IG2DNode node = this;
382         while(node != null) {
383             shape = node.getTransform().createTransformedShape(shape);
384             node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure
385         }
386
387         return shape.getBounds2D();
388     }
389
390     public Point2D controlToLocal(Point2D point) {
391         AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
392         if (at == null)
393             return point;
394         return at.transform(point, null);
395     }
396
397     public Rectangle2D controlToLocal(Rectangle2D rect) {
398         AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
399         if (at == null)
400             return rect;
401         return GeometryUtils.transformRectangle(at, rect);
402     }
403
404     /**
405      * Damn slow method for picking node
406      * 
407      * @param point
408      * @return
409      */
410     public IG2DNode pickNode(Point2D point) {
411         Point2D localpoint = parentToLocal(point);
412         IG2DNode[] nodes = getSortedNodes();
413
414         for(int i = nodes.length-1; i >= 0; i--) {
415             IG2DNode n = nodes[i]; // Reverse order..
416             if(n instanceof G2DParentNode) {
417                 IG2DNode node = ((G2DParentNode)n).pickNode(localpoint);
418                 if(node != null)
419                     return node;
420             } else if(n.contains(localpoint)) {
421                 return n;
422             }
423         }
424         return null;
425     }
426
427     @Override
428     public String toString() {
429         return super.toString() + " [z=" + z + ", transform=" + transform + "]";
430     }
431
432     /**
433      * TODO: not sure if this is a good idea at all.
434      * 
435      * @see org.simantics.scenegraph.utils.InitValueSupport#initValues()
436      */
437     @Override
438     public void initValues() {
439         for (IG2DNode node : getSortedNodes()) {
440             if (node instanceof InitValueSupport) {
441                 ((InitValueSupport) node).initValues();
442             }
443         }
444     }
445
446     /**
447      * @see org.simantics.scenegraph.g2d.IG2DNode#getRootNode()
448      */
449     public G2DSceneGraph getRootNode2D() {
450         ParentNode<?> root = getRootNode();
451         return (G2DSceneGraph) root;
452     }
453
454     @Override
455     public boolean hasFocus() {
456         return getFocusNode() == this;
457     }
458
459     @Override
460     public IG2DNode getFocusNode() {
461         return getRootNode2D().getFocusNode();
462     }
463
464     @Override
465     public void setFocusNode(IG2DNode node) {
466         getRootNode2D().setFocusNode(node);
467     }
468
469     protected NodeEventHandler getEventHandler() {
470         return NodeUtil.getNodeEventHandler(this);
471     }
472
473     protected void addEventHandler(IEventHandler handler) {
474         getEventHandler().add(handler);
475     }
476
477     protected void removeEventHandler(IEventHandler handler) {
478         getEventHandler().remove(handler);
479     }
480
481     @Override
482     public int getEventMask() {
483         return 0;
484     }
485
486     @Override
487     public boolean handleEvent(Event e) {
488         int eventType = EventTypes.toType(e);
489         switch (eventType) {
490             case EventTypes.Command:
491                 return handleCommand((CommandEvent) e);
492
493             case EventTypes.FocusGained:
494             case EventTypes.FocusLost:
495                 return handleFocusEvent((FocusEvent) e);
496
497             case EventTypes.KeyPressed:
498                 return keyPressed((KeyPressedEvent) e);
499             case EventTypes.KeyReleased:
500                 return keyReleased((KeyReleasedEvent) e);
501
502             case EventTypes.MouseButtonPressed:
503                 return mouseButtonPressed((MouseButtonPressedEvent) e);
504             case EventTypes.MouseButtonReleased:
505                 return mouseButtonReleased((MouseButtonReleasedEvent) e);
506             case EventTypes.MouseClick:
507                 return mouseClicked((MouseClickEvent) e);
508             case EventTypes.MouseDoubleClick:
509                 return mouseDoubleClicked((MouseDoubleClickedEvent) e);
510             case EventTypes.MouseMoved:
511                 return mouseMoved((MouseMovedEvent) e);
512             case EventTypes.MouseDragBegin:
513                 return mouseDragged((MouseDragBegin) e);
514             case EventTypes.MouseEnter:
515                 return mouseEntered((MouseEnterEvent) e);
516             case EventTypes.MouseExit:
517                 return mouseExited((MouseExitEvent) e);
518             case EventTypes.MouseWheel:
519                 return mouseWheelMoved((MouseWheelMovedEvent) e);
520
521             case EventTypes.Time:
522                 return handleTimeEvent((TimeEvent) e);
523         }
524         return false;
525     }
526
527     protected boolean keyReleased(KeyReleasedEvent e) {
528         return false;
529     }
530
531     protected boolean keyPressed(KeyPressedEvent e) {
532         return false;
533     }
534
535     protected boolean handleCommand(CommandEvent e) {
536         return false;
537     }
538
539     protected boolean handleFocusEvent(FocusEvent e) {
540         return false;
541     }
542
543     protected boolean handleKeyEvent(KeyEvent e) {
544         return false;
545     }
546
547     protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
548         return false;
549     }
550
551     protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
552         return false;
553     }
554
555     protected boolean mouseClicked(MouseClickEvent e) {
556         return false;
557     }
558
559     protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {
560         return false;
561     }
562
563     protected boolean mouseMoved(MouseMovedEvent e) {
564         return false;
565     }
566
567     protected boolean mouseDragged(MouseDragBegin e) {
568         return false;
569     }
570
571     protected boolean mouseEntered(MouseEnterEvent e) {
572         return false;
573     }
574
575     protected boolean mouseExited(MouseExitEvent e) {
576         return false;
577     }
578
579     protected boolean mouseWheelMoved(MouseWheelMovedEvent e) {
580         return false;
581     }
582
583     protected boolean handleTimeEvent(TimeEvent e) {
584         return false;
585     }
586
587     protected void setCursor(int cursorType) {
588         Container rootPane = NodeUtil.findRootPane(this);
589         if (rootPane != null)
590             rootPane.setCursor(Cursor.getPredefinedCursor(cursorType));
591     }
592
593     protected void setCursor(Cursor cursor) {
594         Container rootPane = NodeUtil.findRootPane(this);
595         if (rootPane != null)
596             rootPane.setCursor(cursor);
597     }
598
599         @Override
600         public Function1<Object, Boolean> getPropertyFunction(String propertyName) {
601                 return ScenegraphUtils.getMethodPropertyFunction(AWTThread.getThreadAccess(), this, propertyName);
602         }
603         
604         @Override
605         public <T> T getProperty(String propertyName) {
606                 return null;
607         }
608         
609         @Override
610         public void setPropertyCallback(Function2<String, Object, Boolean> callback) {
611         }
612     
613         public void synchronizeTransform(double[] data) {
614                 this.setTransform(new AffineTransform(data));
615         }
616
617 }