--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2011 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.g2d;\r
+\r
+import java.awt.Container;\r
+import java.awt.Cursor;\r
+import java.awt.Graphics2D;\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.NoninvertibleTransformException;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.Arrays;\r
+import java.util.Comparator;\r
+import java.util.Iterator;\r
+\r
+import org.simantics.scenegraph.INode;\r
+import org.simantics.scenegraph.LoaderNode;\r
+import org.simantics.scenegraph.ParentNode;\r
+import org.simantics.scenegraph.ScenegraphUtils;\r
+import org.simantics.scenegraph.g2d.events.Event;\r
+import org.simantics.scenegraph.g2d.events.EventTypes;\r
+import org.simantics.scenegraph.g2d.events.FocusEvent;\r
+import org.simantics.scenegraph.g2d.events.IEventHandler;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseWheelMovedEvent;\r
+import org.simantics.scenegraph.g2d.events.NodeEventHandler;\r
+import org.simantics.scenegraph.g2d.events.TimeEvent;\r
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
+import org.simantics.scenegraph.utils.GeometryUtils;\r
+import org.simantics.scenegraph.utils.InitValueSupport;\r
+import org.simantics.scenegraph.utils.NodeUtil;\r
+import org.simantics.scl.runtime.function.Function1;\r
+import org.simantics.scl.runtime.function.Function2;\r
+import org.simantics.utils.threads.AWTThread;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class G2DParentNode extends ParentNode<IG2DNode> implements IG2DNode, InitValueSupport, LoaderNode {\r
+\r
+ private static final long serialVersionUID = 4966823616578337420L;\r
+\r
+ protected static final IG2DNode[] EMPTY_NODE_ARRAY = {};\r
+ protected static final String[] EMPTY_STRING_ARRAY = {};\r
+\r
+ private transient volatile String[] sortedChildrenIds = null;\r
+ private transient volatile IG2DNode[] sortedChildren = null;\r
+\r
+ protected AffineTransform transform = IdentityAffineTransform.INSTANCE;\r
+\r
+ /**\r
+ * Z-index of this node. Default value is 0.\r
+ */\r
+ protected int z = 0;\r
+\r
+ public void invalidateChildOrder() {\r
+ sortedChildrenIds = null;\r
+ sortedChildren = null;\r
+ }\r
+\r
+ @Override\r
+ protected void childrenChanged() {\r
+ invalidateChildOrder();\r
+ }\r
+\r
+ @Override\r
+ @SyncField("z")\r
+ public void setZIndex(int z) {\r
+ if (z != this.z) {\r
+ G2DParentNode parent = (G2DParentNode) getParent();\r
+ if (parent != null)\r
+ parent.invalidateChildOrder();\r
+ this.z = z;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public int getZIndex() {\r
+ return z;\r
+ }\r
+\r
+ @Override\r
+ public boolean validate() {\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public void render(Graphics2D g2d) {\r
+ AffineTransform ot = null;\r
+ if (!transform.isIdentity()) {\r
+ ot = g2d.getTransform();\r
+ g2d.transform(transform);\r
+ }\r
+\r
+ for (IG2DNode node : getSortedNodes()) {\r
+ if (node.validate()) {\r
+ node.render(g2d);\r
+ }\r
+ }\r
+\r
+ if (ot != null)\r
+ g2d.setTransform(ot);\r
+ }\r
+\r
+ /**\r
+ * Return the IDs of the children of this node in ascending Z order. This\r
+ * method will always allocate a new result list and sort it. To get the IDs\r
+ * of the child nodes you need to use this method. Otherwise use\r
+ * {@link #getSortedNodes()} instead, it is faster more memory efficient.\r
+ * \r
+ * @return child node IDs in ascending Z order\r
+ */\r
+ public String[] getSortedNodesById() {\r
+ if (sortedChildrenIds != null)\r
+ return sortedChildrenIds;\r
+ if (children.isEmpty())\r
+ return EMPTY_STRING_ARRAY;\r
+\r
+ String[] sorted = null;\r
+ synchronized (children) {\r
+ if (sortedChildrenIds != null)\r
+ return sortedChildrenIds;\r
+ sorted = children.keySet().toArray(EMPTY_STRING_ARRAY);\r
+ Arrays.sort(sorted, new Comparator<String>() {\r
+ @Override\r
+ public int compare(String a, String b) {\r
+ int za = getNode(a).getZIndex();\r
+ int zb = getNode(b).getZIndex();\r
+ return za < zb ? -1 : (za == zb ? 0 : 1);\r
+ }\r
+ });\r
+ sortedChildrenIds = sorted;\r
+ }\r
+ return sorted;\r
+ }\r
+\r
+ public static final Comparator<IG2DNode> G2DNODE_Z_COMPARATOR = new Comparator<IG2DNode>() {\r
+ @Override\r
+ public int compare(IG2DNode a, IG2DNode b) {\r
+ int za = a.getZIndex();\r
+ int zb = b.getZIndex();\r
+ return za < zb ? -1 : (za == zb ? 0 : 1);\r
+ }\r
+ };\r
+\r
+ /**\r
+ * @return child nodes in ascending Z order\r
+ */\r
+ public IG2DNode[] getSortedNodes() {\r
+ if (sortedChildren != null)\r
+ return sortedChildren;\r
+ if (children.isEmpty())\r
+ return EMPTY_NODE_ARRAY;\r
+\r
+ IG2DNode[] sorted = null;\r
+ synchronized (children) {\r
+ if (sortedChildren != null)\r
+ return sortedChildren;\r
+ sorted = children.values().toArray(EMPTY_NODE_ARRAY);\r
+ Arrays.sort(sorted, G2DNODE_Z_COMPARATOR);\r
+ sortedChildren = sorted;\r
+ }\r
+ return sorted;\r
+ }\r
+\r
+ @Override\r
+ public void cleanup() {\r
+ rootNodeCache = DISPOSED;\r
+ sortedChildren = null;\r
+ sortedChildrenIds = null;\r
+ transform = IdentityAffineTransform.INSTANCE;\r
+ super.cleanup();\r
+ }\r
+\r
+ @Override\r
+ public void repaint() {\r
+ INode parent = getParent();\r
+ while(parent != null && !(parent instanceof G2DSceneGraph))\r
+ parent = parent.getParent();\r
+ if(parent == null || ((G2DSceneGraph)parent).getRootPane() == null) return;\r
+ ((G2DSceneGraph)parent).getRootPane().repaint();\r
+ }\r
+\r
+ @Override\r
+ public void asyncRemoveNode(INode node) {\r
+ ParentNode<?> parent = getParent();\r
+ while(parent != null && parent.getParent() != null)\r
+ parent = parent.getParent();\r
+\r
+ if(parent != null) {\r
+ parent.asyncRemoveNode(node); // Pass to root element\r
+ } else {\r
+ // This is root, should do something... (Actually G2DSceneGraph does something)\r
+ }\r
+ }\r
+\r
+ // Bounds and transformation\r
+\r
+ @Override\r
+ public AffineTransform getTransform() {\r
+ return transform;\r
+ }\r
+\r
+ @Override\r
+ @PropertySetter("Transform")\r
+ @SyncField("transform")\r
+ public void setTransform(AffineTransform transform) {\r
+ assert(transform != null);\r
+ if (transform.isIdentity())\r
+ this.transform = IdentityAffineTransform.INSTANCE;\r
+ else\r
+ this.transform = transform;\r
+ }\r
+\r
+ /**\r
+ * Return bounds transformed with local transformation\r
+ * \r
+ * @return transformed bounds\r
+ */\r
+ @Override\r
+ public Rectangle2D getBounds() {\r
+ Rectangle2D local = getBoundsInLocal();\r
+ if (local == null)\r
+ return null;\r
+ // Optimize trivial identity transform case.\r
+ if (transform.isIdentity())\r
+ return local;\r
+ return transform.createTransformedShape(local).getBounds2D();\r
+ }\r
+\r
+ // Helper methods for bounds checking\r
+\r
+ @Override\r
+ public boolean contains(Point2D point) {\r
+ Rectangle2D bounds = getBounds();\r
+ if(bounds == null) return false;\r
+ return bounds.contains(point);\r
+ }\r
+\r
+ @Override\r
+ public boolean intersects(Rectangle2D b) {\r
+ if (b == null)\r
+ return true;\r
+ Rectangle2D a = getBounds();\r
+ if (a == null)\r
+ return true;\r
+ /*\r
+ * Compared to Rectangle2D.intersects, this\r
+ * intersects closed (not open) shapes.\r
+ */\r
+ double ax = a.getX();\r
+ double ay = a.getY();\r
+ double aw = a.getWidth();\r
+ double ah = a.getHeight();\r
+ double bx = b.getX();\r
+ double by = b.getY();\r
+ double bw = b.getWidth();\r
+ double bh = b.getHeight();\r
+ return (ax + aw >= bx &&\r
+ ay + ah >= by &&\r
+ ax <= bx + bw &&\r
+ ay <= by + bh);\r
+ }\r
+\r
+ @Override\r
+ public Point2D localToParent(Point2D point) {\r
+ return transform.transform(point, null);\r
+ }\r
+\r
+ @Override\r
+ public Rectangle2D localToParent(Rectangle2D rect) {\r
+ return transform.createTransformedShape(rect).getBounds2D();\r
+ }\r
+\r
+ @Override\r
+ public Point2D parentToLocal(Point2D point) {\r
+ AffineTransform inverse = null;\r
+ try {\r
+ inverse = transform.createInverse();\r
+ return inverse.transform(point, null);\r
+ } catch (NoninvertibleTransformException e) {\r
+ e.printStackTrace(); // FIXME\r
+ }\r
+ return point;\r
+ }\r
+\r
+ @Override\r
+ public Rectangle2D parentToLocal(Rectangle2D rect) {\r
+ AffineTransform inverse = null;\r
+ try {\r
+ inverse = transform.createInverse();\r
+ return inverse.createTransformedShape(rect).getBounds2D();\r
+ } catch (NoninvertibleTransformException e) {\r
+ e.printStackTrace(); // FIXME\r
+ }\r
+ return rect;\r
+ }\r
+\r
+ @Override\r
+ public Rectangle2D getBoundsInLocal() {\r
+ return getBoundsInLocal(false);\r
+ }\r
+\r
+ @Override\r
+ public Rectangle2D getBoundsInLocal(boolean ignoreNulls) {\r
+ Iterator<IG2DNode> it = getNodes().iterator();\r
+ if(!it.hasNext())\r
+ return null;\r
+ Rectangle2D bounds = null;\r
+ while(it.hasNext()) {\r
+ IG2DNode node = it.next();\r
+ Rectangle2D b = node.getBounds();\r
+ if(b == null && !ignoreNulls)\r
+ return null;\r
+ if(b != null) {\r
+ if(bounds == null) {\r
+ bounds = b.getFrame();\r
+ } else {\r
+ bounds.add(b);\r
+ }\r
+ }\r
+ }\r
+ return bounds;\r
+ }\r
+\r
+ @Override\r
+ public Point2D localToControl(Point2D point) {\r
+ IG2DNode node = this;\r
+ while(node != null) {\r
+ node.getTransform().transform(point, null);\r
+ node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure\r
+ }\r
+ return point;\r
+ }\r
+\r
+ @Override\r
+ public Rectangle2D localToControl(Rectangle2D rect) {\r
+ Shape shape = rect;\r
+ IG2DNode node = this;\r
+ while(node != null) {\r
+ shape = node.getTransform().createTransformedShape(shape);\r
+ node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure\r
+ }\r
+\r
+ return shape.getBounds2D();\r
+ }\r
+\r
+ public Point2D controlToLocal(Point2D point) {\r
+ AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);\r
+ if (at == null)\r
+ return point;\r
+ return at.transform(point, null);\r
+ }\r
+\r
+ public Rectangle2D controlToLocal(Rectangle2D rect) {\r
+ AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);\r
+ if (at == null)\r
+ return rect;\r
+ return GeometryUtils.transformRectangle(at, rect);\r
+ }\r
+\r
+ /**\r
+ * Damn slow method for picking node\r
+ * \r
+ * @param point\r
+ * @return\r
+ */\r
+ public IG2DNode pickNode(Point2D point) {\r
+ Point2D localpoint = parentToLocal(point);\r
+ IG2DNode[] nodes = getSortedNodes();\r
+\r
+ for(int i = nodes.length-1; i >= 0; i--) {\r
+ IG2DNode n = nodes[i]; // Reverse order..\r
+ if(n instanceof G2DParentNode) {\r
+ IG2DNode node = ((G2DParentNode)n).pickNode(localpoint);\r
+ if(node != null)\r
+ return node;\r
+ } else if(n.contains(localpoint)) {\r
+ return n;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return super.toString() + " [z=" + z + ", transform=" + transform + "]";\r
+ }\r
+\r
+ /**\r
+ * TODO: not sure if this is a good idea at all.\r
+ * \r
+ * @see org.simantics.scenegraph.utils.InitValueSupport#initValues()\r
+ */\r
+ @Override\r
+ public void initValues() {\r
+ for (IG2DNode node : getSortedNodes()) {\r
+ if (node instanceof InitValueSupport) {\r
+ ((InitValueSupport) node).initValues();\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @see org.simantics.scenegraph.g2d.IG2DNode#getRootNode()\r
+ */\r
+ public G2DSceneGraph getRootNode2D() {\r
+ ParentNode<?> root = getRootNode();\r
+ return (G2DSceneGraph) root;\r
+ }\r
+\r
+ @Override\r
+ public boolean hasFocus() {\r
+ return getFocusNode() == this;\r
+ }\r
+\r
+ @Override\r
+ public IG2DNode getFocusNode() {\r
+ return getRootNode2D().getFocusNode();\r
+ }\r
+\r
+ @Override\r
+ public void setFocusNode(IG2DNode node) {\r
+ getRootNode2D().setFocusNode(node);\r
+ }\r
+\r
+ protected NodeEventHandler getEventHandler() {\r
+ return NodeUtil.getNodeEventHandler(this);\r
+ }\r
+\r
+ protected void addEventHandler(IEventHandler handler) {\r
+ getEventHandler().add(handler);\r
+ }\r
+\r
+ protected void removeEventHandler(IEventHandler handler) {\r
+ getEventHandler().remove(handler);\r
+ }\r
+\r
+ @Override\r
+ public int getEventMask() {\r
+ return 0;\r
+ }\r
+\r
+ @Override\r
+ public boolean handleEvent(Event e) {\r
+ int eventType = EventTypes.toType(e);\r
+ switch (eventType) {\r
+ case EventTypes.Command:\r
+ return handleCommand((CommandEvent) e);\r
+\r
+ case EventTypes.FocusGained:\r
+ case EventTypes.FocusLost:\r
+ return handleFocusEvent((FocusEvent) e);\r
+\r
+ case EventTypes.KeyPressed:\r
+ return keyPressed((KeyPressedEvent) e);\r
+ case EventTypes.KeyReleased:\r
+ return keyReleased((KeyReleasedEvent) e);\r
+\r
+ case EventTypes.MouseButtonPressed:\r
+ return mouseButtonPressed((MouseButtonPressedEvent) e);\r
+ case EventTypes.MouseButtonReleased:\r
+ return mouseButtonReleased((MouseButtonReleasedEvent) e);\r
+ case EventTypes.MouseClick:\r
+ return mouseClicked((MouseClickEvent) e);\r
+ case EventTypes.MouseDoubleClick:\r
+ return mouseDoubleClicked((MouseDoubleClickedEvent) e);\r
+ case EventTypes.MouseMoved:\r
+ return mouseMoved((MouseMovedEvent) e);\r
+ case EventTypes.MouseDragBegin:\r
+ return mouseDragged((MouseDragBegin) e);\r
+ case EventTypes.MouseEnter:\r
+ return mouseEntered((MouseEnterEvent) e);\r
+ case EventTypes.MouseExit:\r
+ return mouseExited((MouseExitEvent) e);\r
+ case EventTypes.MouseWheel:\r
+ return mouseWheelMoved((MouseWheelMovedEvent) e);\r
+\r
+ case EventTypes.Time:\r
+ return handleTimeEvent((TimeEvent) e);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ protected boolean keyReleased(KeyReleasedEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean keyPressed(KeyPressedEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean handleCommand(CommandEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean handleFocusEvent(FocusEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean handleKeyEvent(KeyEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseClicked(MouseClickEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseMoved(MouseMovedEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseDragged(MouseDragBegin e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseEntered(MouseEnterEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseExited(MouseExitEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseWheelMoved(MouseWheelMovedEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected boolean handleTimeEvent(TimeEvent e) {\r
+ return false;\r
+ }\r
+\r
+ protected void setCursor(int cursorType) {\r
+ Container rootPane = NodeUtil.findRootPane(this);\r
+ if (rootPane != null)\r
+ rootPane.setCursor(Cursor.getPredefinedCursor(cursorType));\r
+ }\r
+\r
+ protected void setCursor(Cursor cursor) {\r
+ Container rootPane = NodeUtil.findRootPane(this);\r
+ if (rootPane != null)\r
+ rootPane.setCursor(cursor);\r
+ }\r
+\r
+ @Override\r
+ public Function1<Object, Boolean> getPropertyFunction(String propertyName) {\r
+ return ScenegraphUtils.getMethodPropertyFunction(AWTThread.getThreadAccess(), this, propertyName);\r
+ }\r
+ \r
+ @Override\r
+ public <T> T getProperty(String propertyName) {\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public void setPropertyCallback(Function2<String, Object, Boolean> callback) {\r
+ }\r
+ \r
+ public void synchronizeTransform(double[] data) {\r
+ this.setTransform(new AffineTransform(data));\r
+ }\r
+}\r