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