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