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