1 /*******************************************************************************
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d;
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;
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;
57 * @author Tuukka Lehtonen
59 public class G2DParentNode extends ParentNode<IG2DNode> implements IG2DNode, InitValueSupport, LoaderNode {
61 private static final long serialVersionUID = 4966823616578337420L;
63 protected static final IG2DNode[] EMPTY_NODE_ARRAY = {};
64 protected static final String[] EMPTY_STRING_ARRAY = {};
66 private transient volatile String[] sortedChildrenIds = null;
67 private transient volatile IG2DNode[] sortedChildren = null;
69 protected AffineTransform transform = IdentityAffineTransform.INSTANCE;
72 * Z-index of this node. Default value is 0.
76 public void invalidateChildOrder() {
77 sortedChildrenIds = null;
78 sortedChildren = null;
82 protected void childrenChanged() {
83 invalidateChildOrder();
88 public void setZIndex(int z) {
90 G2DParentNode parent = (G2DParentNode) getParent();
92 parent.invalidateChildOrder();
98 public int getZIndex() {
103 public boolean validate() {
108 public void render(Graphics2D g2d) {
109 AffineTransform ot = null;
110 if (!transform.isIdentity()) {
111 ot = g2d.getTransform();
112 g2d.transform(transform);
115 for (IG2DNode node : getSortedNodes()) {
116 if (node.validate()) {
122 g2d.setTransform(ot);
126 public void refresh() {
127 for (IG2DNode node : getSortedNodes()) {
128 if (node.validate()) {
135 public void accept(IG2DNodeVisitor visitor) {
137 for (IG2DNode node : getSortedNodes()) {
138 if (node.validate()) {
139 node.accept(visitor);
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.
151 * @return child node IDs in ascending Z order
153 public String[] getSortedNodesById() {
154 if (sortedChildrenIds != null)
155 return sortedChildrenIds;
156 if (children.isEmpty())
157 return EMPTY_STRING_ARRAY;
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>() {
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);
172 sortedChildrenIds = sorted;
177 public static final Comparator<IG2DNode> G2DNODE_Z_COMPARATOR = new Comparator<IG2DNode>() {
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);
187 * @return child nodes in ascending Z order
189 public IG2DNode[] getSortedNodes() {
190 if (sortedChildren != null)
191 return sortedChildren;
192 if (children.isEmpty())
193 return EMPTY_NODE_ARRAY;
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;
207 public void cleanup() {
208 rootNodeCache = DISPOSED;
209 sortedChildren = null;
210 sortedChildrenIds = null;
211 transform = IdentityAffineTransform.INSTANCE;
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();
225 public void asyncRemoveNode(INode node) {
226 ParentNode<?> parent = getParent();
227 while(parent != null && parent.getParent() != null)
228 parent = parent.getParent();
231 parent.asyncRemoveNode(node); // Pass to root element
233 // This is root, should do something... (Actually G2DSceneGraph does something)
237 // Bounds and transformation
240 public AffineTransform getTransform() {
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;
252 this.transform = transform;
256 * Return bounds transformed with local transformation
258 * @return transformed bounds
261 public Rectangle2D getBounds() {
262 Rectangle2D local = getBoundsInLocal();
265 // Optimize trivial identity transform case.
266 if (transform.isIdentity())
268 return transform.createTransformedShape(local).getBounds2D();
271 // Helper methods for bounds checking
274 public boolean contains(Point2D point) {
275 Rectangle2D bounds = getBounds();
276 if(bounds == null) return false;
277 return bounds.contains(point);
281 public boolean intersects(Rectangle2D b) {
284 Rectangle2D a = getBounds();
288 * Compared to Rectangle2D.intersects, this
289 * intersects closed (not open) shapes.
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 &&
306 public Point2D localToParent(Point2D point) {
307 return transform.transform(point, null);
311 public Rectangle2D localToParent(Rectangle2D rect) {
312 return transform.createTransformedShape(rect).getBounds2D();
316 public Point2D parentToLocal(Point2D point) {
317 AffineTransform inverse = null;
319 inverse = transform.createInverse();
320 return inverse.transform(point, null);
321 } catch (NoninvertibleTransformException e) {
322 e.printStackTrace(); // FIXME
328 public Rectangle2D parentToLocal(Rectangle2D rect) {
329 AffineTransform inverse = null;
331 inverse = transform.createInverse();
332 return inverse.createTransformedShape(rect).getBounds2D();
333 } catch (NoninvertibleTransformException e) {
334 e.printStackTrace(); // FIXME
340 public Rectangle2D getBoundsInLocal() {
341 return getBoundsInLocal(false);
345 public Rectangle2D getBoundsInLocal(boolean ignoreNulls) {
346 Iterator<IG2DNode> it = getNodes().iterator();
349 Rectangle2D bounds = null;
350 while(it.hasNext()) {
351 IG2DNode node = it.next();
352 Rectangle2D b = node.getBounds();
353 if(b == null && !ignoreNulls)
356 if(!GeometryUtils.isUndefinedRectangle(b)) {
358 bounds = b.getFrame();
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
379 public Rectangle2D localToControl(Rectangle2D 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
387 return shape.getBounds2D();
390 public Point2D controlToLocal(Point2D point) {
391 AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
394 return at.transform(point, null);
397 public Rectangle2D controlToLocal(Rectangle2D rect) {
398 AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
401 return GeometryUtils.transformRectangle(at, rect);
405 * Damn slow method for picking node
410 public IG2DNode pickNode(Point2D point) {
411 Point2D localpoint = parentToLocal(point);
412 IG2DNode[] nodes = getSortedNodes();
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);
420 } else if(n.contains(localpoint)) {
428 public String toString() {
429 return super.toString() + " [z=" + z + ", transform=" + transform + "]";
433 * TODO: not sure if this is a good idea at all.
435 * @see org.simantics.scenegraph.utils.InitValueSupport#initValues()
438 public void initValues() {
439 for (IG2DNode node : getSortedNodes()) {
440 if (node instanceof InitValueSupport) {
441 ((InitValueSupport) node).initValues();
447 * @see org.simantics.scenegraph.g2d.IG2DNode#getRootNode()
449 public G2DSceneGraph getRootNode2D() {
450 ParentNode<?> root = getRootNode();
451 return (G2DSceneGraph) root;
455 public boolean hasFocus() {
456 return getFocusNode() == this;
460 public IG2DNode getFocusNode() {
461 return getRootNode2D().getFocusNode();
465 public void setFocusNode(IG2DNode node) {
466 getRootNode2D().setFocusNode(node);
469 protected NodeEventHandler getEventHandler() {
470 return NodeUtil.getNodeEventHandler(this);
473 protected void addEventHandler(IEventHandler handler) {
474 getEventHandler().add(handler);
477 protected void removeEventHandler(IEventHandler handler) {
478 getEventHandler().remove(handler);
482 public int getEventMask() {
487 public boolean handleEvent(Event e) {
488 int eventType = EventTypes.toType(e);
490 case EventTypes.Command:
491 return handleCommand((CommandEvent) e);
493 case EventTypes.FocusGained:
494 case EventTypes.FocusLost:
495 return handleFocusEvent((FocusEvent) e);
497 case EventTypes.KeyPressed:
498 return keyPressed((KeyPressedEvent) e);
499 case EventTypes.KeyReleased:
500 return keyReleased((KeyReleasedEvent) e);
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);
521 case EventTypes.Time:
522 return handleTimeEvent((TimeEvent) e);
527 protected boolean keyReleased(KeyReleasedEvent e) {
531 protected boolean keyPressed(KeyPressedEvent e) {
535 protected boolean handleCommand(CommandEvent e) {
539 protected boolean handleFocusEvent(FocusEvent e) {
543 protected boolean handleKeyEvent(KeyEvent e) {
547 protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
551 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
555 protected boolean mouseClicked(MouseClickEvent e) {
559 protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {
563 protected boolean mouseMoved(MouseMovedEvent e) {
567 protected boolean mouseDragged(MouseDragBegin e) {
571 protected boolean mouseEntered(MouseEnterEvent e) {
575 protected boolean mouseExited(MouseExitEvent e) {
579 protected boolean mouseWheelMoved(MouseWheelMovedEvent e) {
583 protected boolean handleTimeEvent(TimeEvent e) {
587 protected void setCursor(int cursorType) {
588 Container rootPane = NodeUtil.findRootPane(this);
589 if (rootPane != null)
590 rootPane.setCursor(Cursor.getPredefinedCursor(cursorType));
593 protected void setCursor(Cursor cursor) {
594 Container rootPane = NodeUtil.findRootPane(this);
595 if (rootPane != null)
596 rootPane.setCursor(cursor);
600 public Function1<Object, Boolean> getPropertyFunction(String propertyName) {
601 return ScenegraphUtils.getMethodPropertyFunction(AWTThread.getThreadAccess(), this, propertyName);
605 public <T> T getProperty(String propertyName) {
610 public void setPropertyCallback(Function2<String, Object, Boolean> callback) {
613 public void synchronizeTransform(double[] data) {
614 this.setTransform(new AffineTransform(data));