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