1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.g2d;
\r
14 import java.awt.Container;
\r
15 import java.awt.Cursor;
\r
16 import java.awt.Graphics2D;
\r
17 import java.awt.Shape;
\r
18 import java.awt.geom.AffineTransform;
\r
19 import java.awt.geom.NoninvertibleTransformException;
\r
20 import java.awt.geom.Point2D;
\r
21 import java.awt.geom.Rectangle2D;
\r
22 import java.util.Arrays;
\r
23 import java.util.Comparator;
\r
24 import java.util.Iterator;
\r
26 import org.simantics.scenegraph.INode;
\r
27 import org.simantics.scenegraph.LoaderNode;
\r
28 import org.simantics.scenegraph.ParentNode;
\r
29 import org.simantics.scenegraph.ScenegraphUtils;
\r
30 import org.simantics.scenegraph.g2d.events.Event;
\r
31 import org.simantics.scenegraph.g2d.events.EventTypes;
\r
32 import org.simantics.scenegraph.g2d.events.FocusEvent;
\r
33 import org.simantics.scenegraph.g2d.events.IEventHandler;
\r
34 import org.simantics.scenegraph.g2d.events.KeyEvent;
\r
35 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
\r
36 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
\r
37 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
38 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
\r
39 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
\r
40 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;
\r
41 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
42 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;
\r
43 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
\r
44 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
45 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseWheelMovedEvent;
\r
46 import org.simantics.scenegraph.g2d.events.NodeEventHandler;
\r
47 import org.simantics.scenegraph.g2d.events.TimeEvent;
\r
48 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
\r
49 import org.simantics.scenegraph.utils.GeometryUtils;
\r
50 import org.simantics.scenegraph.utils.InitValueSupport;
\r
51 import org.simantics.scenegraph.utils.NodeUtil;
\r
52 import org.simantics.scl.runtime.function.Function1;
\r
53 import org.simantics.scl.runtime.function.Function2;
\r
54 import org.simantics.utils.threads.AWTThread;
\r
57 * @author Tuukka Lehtonen
\r
59 public class G2DParentNode extends ParentNode<IG2DNode> implements IG2DNode, InitValueSupport, LoaderNode {
\r
61 private static final long serialVersionUID = 4966823616578337420L;
\r
63 protected static final IG2DNode[] EMPTY_NODE_ARRAY = {};
\r
64 protected static final String[] EMPTY_STRING_ARRAY = {};
\r
66 private transient volatile String[] sortedChildrenIds = null;
\r
67 private transient volatile IG2DNode[] sortedChildren = null;
\r
69 protected AffineTransform transform = IdentityAffineTransform.INSTANCE;
\r
72 * Z-index of this node. Default value is 0.
\r
74 protected int z = 0;
\r
76 public void invalidateChildOrder() {
\r
77 sortedChildrenIds = null;
\r
78 sortedChildren = null;
\r
82 protected void childrenChanged() {
\r
83 invalidateChildOrder();
\r
88 public void setZIndex(int z) {
\r
90 G2DParentNode parent = (G2DParentNode) getParent();
\r
92 parent.invalidateChildOrder();
\r
98 public int getZIndex() {
\r
103 public boolean validate() {
\r
108 public void render(Graphics2D g2d) {
\r
109 AffineTransform ot = null;
\r
110 if (!transform.isIdentity()) {
\r
111 ot = g2d.getTransform();
\r
112 g2d.transform(transform);
\r
115 for (IG2DNode node : getSortedNodes()) {
\r
116 if (node.validate()) {
\r
122 g2d.setTransform(ot);
\r
126 * Return the IDs of the children of this node in ascending Z order. This
\r
127 * method will always allocate a new result list and sort it. To get the IDs
\r
128 * of the child nodes you need to use this method. Otherwise use
\r
129 * {@link #getSortedNodes()} instead, it is faster more memory efficient.
\r
131 * @return child node IDs in ascending Z order
\r
133 public String[] getSortedNodesById() {
\r
134 if (sortedChildrenIds != null)
\r
135 return sortedChildrenIds;
\r
136 if (children.isEmpty())
\r
137 return EMPTY_STRING_ARRAY;
\r
139 String[] sorted = null;
\r
140 synchronized (children) {
\r
141 if (sortedChildrenIds != null)
\r
142 return sortedChildrenIds;
\r
143 sorted = children.keySet().toArray(EMPTY_STRING_ARRAY);
\r
144 Arrays.sort(sorted, new Comparator<String>() {
\r
146 public int compare(String a, String b) {
\r
147 int za = getNode(a).getZIndex();
\r
148 int zb = getNode(b).getZIndex();
\r
149 return za < zb ? -1 : (za == zb ? 0 : 1);
\r
152 sortedChildrenIds = sorted;
\r
157 public static final Comparator<IG2DNode> G2DNODE_Z_COMPARATOR = new Comparator<IG2DNode>() {
\r
159 public int compare(IG2DNode a, IG2DNode b) {
\r
160 int za = a.getZIndex();
\r
161 int zb = b.getZIndex();
\r
162 return za < zb ? -1 : (za == zb ? 0 : 1);
\r
167 * @return child nodes in ascending Z order
\r
169 public IG2DNode[] getSortedNodes() {
\r
170 if (sortedChildren != null)
\r
171 return sortedChildren;
\r
172 if (children.isEmpty())
\r
173 return EMPTY_NODE_ARRAY;
\r
175 IG2DNode[] sorted = null;
\r
176 synchronized (children) {
\r
177 if (sortedChildren != null)
\r
178 return sortedChildren;
\r
179 sorted = children.values().toArray(EMPTY_NODE_ARRAY);
\r
180 Arrays.sort(sorted, G2DNODE_Z_COMPARATOR);
\r
181 sortedChildren = sorted;
\r
187 public void cleanup() {
\r
188 rootNodeCache = DISPOSED;
\r
189 sortedChildren = null;
\r
190 sortedChildrenIds = null;
\r
191 transform = IdentityAffineTransform.INSTANCE;
\r
196 public void repaint() {
\r
197 INode parent = getParent();
\r
198 while(parent != null && !(parent instanceof G2DSceneGraph))
\r
199 parent = parent.getParent();
\r
200 if(parent == null || ((G2DSceneGraph)parent).getRootPane() == null) return;
\r
201 ((G2DSceneGraph)parent).getRootPane().repaint();
\r
205 public void asyncRemoveNode(INode node) {
\r
206 ParentNode<?> parent = getParent();
\r
207 while(parent != null && parent.getParent() != null)
\r
208 parent = parent.getParent();
\r
210 if(parent != null) {
\r
211 parent.asyncRemoveNode(node); // Pass to root element
\r
213 // This is root, should do something... (Actually G2DSceneGraph does something)
\r
217 // Bounds and transformation
\r
220 public AffineTransform getTransform() {
\r
225 @PropertySetter("Transform")
\r
226 @SyncField("transform")
\r
227 public void setTransform(AffineTransform transform) {
\r
228 assert(transform != null);
\r
229 if (transform.isIdentity())
\r
230 this.transform = IdentityAffineTransform.INSTANCE;
\r
232 this.transform = transform;
\r
236 * Return bounds transformed with local transformation
\r
238 * @return transformed bounds
\r
241 public Rectangle2D getBounds() {
\r
242 Rectangle2D local = getBoundsInLocal();
\r
245 // Optimize trivial identity transform case.
\r
246 if (transform.isIdentity())
\r
248 return transform.createTransformedShape(local).getBounds2D();
\r
251 // Helper methods for bounds checking
\r
254 public boolean contains(Point2D point) {
\r
255 Rectangle2D bounds = getBounds();
\r
256 if(bounds == null) return false;
\r
257 return bounds.contains(point);
\r
261 public boolean intersects(Rectangle2D b) {
\r
264 Rectangle2D a = getBounds();
\r
268 * Compared to Rectangle2D.intersects, this
\r
269 * intersects closed (not open) shapes.
\r
271 double ax = a.getX();
\r
272 double ay = a.getY();
\r
273 double aw = a.getWidth();
\r
274 double ah = a.getHeight();
\r
275 double bx = b.getX();
\r
276 double by = b.getY();
\r
277 double bw = b.getWidth();
\r
278 double bh = b.getHeight();
\r
279 return (ax + aw >= bx &&
\r
286 public Point2D localToParent(Point2D point) {
\r
287 return transform.transform(point, null);
\r
291 public Rectangle2D localToParent(Rectangle2D rect) {
\r
292 return transform.createTransformedShape(rect).getBounds2D();
\r
296 public Point2D parentToLocal(Point2D point) {
\r
297 AffineTransform inverse = null;
\r
299 inverse = transform.createInverse();
\r
300 return inverse.transform(point, null);
\r
301 } catch (NoninvertibleTransformException e) {
\r
302 e.printStackTrace(); // FIXME
\r
308 public Rectangle2D parentToLocal(Rectangle2D rect) {
\r
309 AffineTransform inverse = null;
\r
311 inverse = transform.createInverse();
\r
312 return inverse.createTransformedShape(rect).getBounds2D();
\r
313 } catch (NoninvertibleTransformException e) {
\r
314 e.printStackTrace(); // FIXME
\r
320 public Rectangle2D getBoundsInLocal() {
\r
321 return getBoundsInLocal(false);
\r
325 public Rectangle2D getBoundsInLocal(boolean ignoreNulls) {
\r
326 Iterator<IG2DNode> it = getNodes().iterator();
\r
329 Rectangle2D bounds = null;
\r
330 while(it.hasNext()) {
\r
331 IG2DNode node = it.next();
\r
332 Rectangle2D b = node.getBounds();
\r
333 if(b == null && !ignoreNulls)
\r
336 if(bounds == null) {
\r
337 bounds = b.getFrame();
\r
347 public Point2D localToControl(Point2D point) {
\r
348 IG2DNode node = this;
\r
349 while(node != null) {
\r
350 node.getTransform().transform(point, null);
\r
351 node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure
\r
357 public Rectangle2D localToControl(Rectangle2D rect) {
\r
358 Shape shape = rect;
\r
359 IG2DNode node = this;
\r
360 while(node != null) {
\r
361 shape = node.getTransform().createTransformedShape(shape);
\r
362 node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure
\r
365 return shape.getBounds2D();
\r
368 public Point2D controlToLocal(Point2D point) {
\r
369 AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
\r
372 return at.transform(point, null);
\r
375 public Rectangle2D controlToLocal(Rectangle2D rect) {
\r
376 AffineTransform at = NodeUtil.getGlobalToLocalTransform(this, null);
\r
379 return GeometryUtils.transformRectangle(at, rect);
\r
383 * Damn slow method for picking node
\r
388 public IG2DNode pickNode(Point2D point) {
\r
389 Point2D localpoint = parentToLocal(point);
\r
390 IG2DNode[] nodes = getSortedNodes();
\r
392 for(int i = nodes.length-1; i >= 0; i--) {
\r
393 IG2DNode n = nodes[i]; // Reverse order..
\r
394 if(n instanceof G2DParentNode) {
\r
395 IG2DNode node = ((G2DParentNode)n).pickNode(localpoint);
\r
398 } else if(n.contains(localpoint)) {
\r
406 public String toString() {
\r
407 return super.toString() + " [z=" + z + ", transform=" + transform + "]";
\r
411 * TODO: not sure if this is a good idea at all.
\r
413 * @see org.simantics.scenegraph.utils.InitValueSupport#initValues()
\r
416 public void initValues() {
\r
417 for (IG2DNode node : getSortedNodes()) {
\r
418 if (node instanceof InitValueSupport) {
\r
419 ((InitValueSupport) node).initValues();
\r
425 * @see org.simantics.scenegraph.g2d.IG2DNode#getRootNode()
\r
427 public G2DSceneGraph getRootNode2D() {
\r
428 ParentNode<?> root = getRootNode();
\r
429 return (G2DSceneGraph) root;
\r
433 public boolean hasFocus() {
\r
434 return getFocusNode() == this;
\r
438 public IG2DNode getFocusNode() {
\r
439 return getRootNode2D().getFocusNode();
\r
443 public void setFocusNode(IG2DNode node) {
\r
444 getRootNode2D().setFocusNode(node);
\r
447 protected NodeEventHandler getEventHandler() {
\r
448 return NodeUtil.getNodeEventHandler(this);
\r
451 protected void addEventHandler(IEventHandler handler) {
\r
452 getEventHandler().add(handler);
\r
455 protected void removeEventHandler(IEventHandler handler) {
\r
456 getEventHandler().remove(handler);
\r
460 public int getEventMask() {
\r
465 public boolean handleEvent(Event e) {
\r
466 int eventType = EventTypes.toType(e);
\r
467 switch (eventType) {
\r
468 case EventTypes.Command:
\r
469 return handleCommand((CommandEvent) e);
\r
471 case EventTypes.FocusGained:
\r
472 case EventTypes.FocusLost:
\r
473 return handleFocusEvent((FocusEvent) e);
\r
475 case EventTypes.KeyPressed:
\r
476 return keyPressed((KeyPressedEvent) e);
\r
477 case EventTypes.KeyReleased:
\r
478 return keyReleased((KeyReleasedEvent) e);
\r
480 case EventTypes.MouseButtonPressed:
\r
481 return mouseButtonPressed((MouseButtonPressedEvent) e);
\r
482 case EventTypes.MouseButtonReleased:
\r
483 return mouseButtonReleased((MouseButtonReleasedEvent) e);
\r
484 case EventTypes.MouseClick:
\r
485 return mouseClicked((MouseClickEvent) e);
\r
486 case EventTypes.MouseDoubleClick:
\r
487 return mouseDoubleClicked((MouseDoubleClickedEvent) e);
\r
488 case EventTypes.MouseMoved:
\r
489 return mouseMoved((MouseMovedEvent) e);
\r
490 case EventTypes.MouseDragBegin:
\r
491 return mouseDragged((MouseDragBegin) e);
\r
492 case EventTypes.MouseEnter:
\r
493 return mouseEntered((MouseEnterEvent) e);
\r
494 case EventTypes.MouseExit:
\r
495 return mouseExited((MouseExitEvent) e);
\r
496 case EventTypes.MouseWheel:
\r
497 return mouseWheelMoved((MouseWheelMovedEvent) e);
\r
499 case EventTypes.Time:
\r
500 return handleTimeEvent((TimeEvent) e);
\r
505 protected boolean keyReleased(KeyReleasedEvent e) {
\r
509 protected boolean keyPressed(KeyPressedEvent e) {
\r
513 protected boolean handleCommand(CommandEvent e) {
\r
517 protected boolean handleFocusEvent(FocusEvent e) {
\r
521 protected boolean handleKeyEvent(KeyEvent e) {
\r
525 protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
\r
529 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
\r
533 protected boolean mouseClicked(MouseClickEvent e) {
\r
537 protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {
\r
541 protected boolean mouseMoved(MouseMovedEvent e) {
\r
545 protected boolean mouseDragged(MouseDragBegin e) {
\r
549 protected boolean mouseEntered(MouseEnterEvent e) {
\r
553 protected boolean mouseExited(MouseExitEvent e) {
\r
557 protected boolean mouseWheelMoved(MouseWheelMovedEvent e) {
\r
561 protected boolean handleTimeEvent(TimeEvent e) {
\r
565 protected void setCursor(int cursorType) {
\r
566 Container rootPane = NodeUtil.findRootPane(this);
\r
567 if (rootPane != null)
\r
568 rootPane.setCursor(Cursor.getPredefinedCursor(cursorType));
\r
571 protected void setCursor(Cursor cursor) {
\r
572 Container rootPane = NodeUtil.findRootPane(this);
\r
573 if (rootPane != null)
\r
574 rootPane.setCursor(cursor);
\r
578 public Function1<Object, Boolean> getPropertyFunction(String propertyName) {
\r
579 return ScenegraphUtils.getMethodPropertyFunction(AWTThread.getThreadAccess(), this, propertyName);
\r
583 public <T> T getProperty(String propertyName) {
\r
588 public void setPropertyCallback(Function2<String, Object, Boolean> callback) {
\r
591 public void synchronizeTransform(double[] data) {
\r
592 this.setTransform(new AffineTransform(data));
\r