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