/******************************************************************************* * Copyright (c) 2007, 2010 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.Component; import java.awt.Cursor; 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 org.simantics.scenegraph.INode; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.ParentNode; 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.NodeUtil; public abstract class G2DNode extends Node implements IG2DNode { private static final long serialVersionUID = 8283264115992894707L; protected int z = 0; protected AffineTransform transform = IdentityAffineTransform.INSTANCE; @SyncField("z") public void setZIndex(int z) { if (z != this.z) { G2DParentNode parent = (G2DParentNode) getParent(); if (parent != null) parent.invalidateChildOrder(); this.z = z; } } public int getZIndex() { return z; } public boolean validate() { return true; } @Override public void cleanup() { retractMapping(); repaint(); } 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(); } // Bounds and transformation public AffineTransform getTransform() { return transform; } @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 */ public Rectangle2D getBounds() { Rectangle2D local = getBoundsInLocal(); if (local == null) return null; // TODO: potential spot for CPU/memory allocation optimization // by using more specialized implementations return transform.createTransformedShape(local).getBounds2D(); } /** * Return bounds in local coordinates * * @return bounds */ abstract public Rectangle2D getBoundsInLocal(); public Rectangle2D getBoundsInLocal(boolean ignoreNulls) { return getBoundsInLocal(); } // Helper methods for bounds checking public boolean contains(Point2D point) { Rectangle2D bounds = getBounds(); if(bounds == null) return false; return bounds.contains(point); } public boolean containsLocal(Point2D localPoint) { Rectangle2D bounds = getBoundsInLocal(); if(bounds == null) return false; return bounds.contains(localPoint); } 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); } public Point2D localToParent(Point2D point) { return transform.transform(point, null); } public Rectangle2D localToParent(Rectangle2D rect) { return transform.createTransformedShape(rect).getBounds2D(); } 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; } public Point2D parentToLocal(Point2D src, Point2D dst) { AffineTransform inverse = null; try { inverse = transform.createInverse(); return inverse.transform(src, dst); } catch (NoninvertibleTransformException e) { e.printStackTrace(); // FIXME } return src; } 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; } public Point2D localToControl(Point2D point) { IG2DNode node = this; while(node != null) { point = node.getTransform().transform(point, null); node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure } return point; } 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); } @Override public String toString() { return super.toString() + " [z=" + z + ",transform=" + transform + "]"; } /** * @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) { Component rootPane = NodeUtil.findRootPane(this); if (rootPane != null) rootPane.setCursor(Cursor.getPredefinedCursor(cursorType)); } protected void setCursor(Cursor cursor) { Component rootPane = NodeUtil.findRootPane(this); if (rootPane != null) rootPane.setCursor(cursor); } }