]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DParentNode.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / G2DParentNode.java
diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DParentNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DParentNode.java
new file mode 100644 (file)
index 0000000..20f8dd4
--- /dev/null
@@ -0,0 +1,594 @@
+/*******************************************************************************\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