+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 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.g2d.canvas.impl;\r
+\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.canvas.ICanvasParticipant;\r
+import org.simantics.g2d.canvas.IContentContext;\r
+import org.simantics.g2d.canvas.IMouseCaptureContext;\r
+import org.simantics.g2d.canvas.IMouseCursorContext;\r
+import org.simantics.g2d.chassis.ITooltipProvider;\r
+import org.simantics.g2d.scenegraph.SceneGraphConstants;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
+import org.simantics.scenegraph.g2d.events.Event;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerStack;\r
+import org.simantics.scenegraph.g2d.events.EventQueue;\r
+import org.simantics.scenegraph.g2d.events.IEventHandlerStack;\r
+import org.simantics.scenegraph.g2d.events.IEventQueue;\r
+import org.simantics.scenegraph.g2d.events.IEventQueue.IEventQueueListener;\r
+import org.simantics.scenegraph.g2d.events.MouseEventCoalescer;\r
+import org.simantics.scenegraph.g2d.nodes.DataNode;\r
+import org.simantics.utils.datastructures.context.Context;\r
+import org.simantics.utils.datastructures.context.IContext;\r
+import org.simantics.utils.datastructures.context.IContextListener;\r
+import org.simantics.utils.datastructures.hints.HintContext;\r
+import org.simantics.utils.datastructures.hints.HintStack;\r
+import org.simantics.utils.datastructures.hints.IHintContext;\r
+import org.simantics.utils.datastructures.hints.IHintStack;\r
+import org.simantics.utils.strings.EString;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+\r
+/**\r
+ * This class contains the UI Support the Elements need when rendering and interacting with \r
+ * the Operating System. \r
+ * \r
+ * @author Toni Kalajainen\r
+ */\r
+public class CanvasContext extends Context<ICanvasParticipant> implements ICanvasContext {\r
+\r
+ protected HintStack hintStack = new HintStack();\r
+ protected HintContext bottomHintContext = new HintContext();\r
+ protected IEventHandlerStack eventHandlerStack = null;\r
+ protected boolean eventHandlingOrdered = false;\r
+ protected EventQueue eventQueue = null;\r
+ protected IContentContext paintableCtx = new PaintableContextImpl();\r
+ protected final IThreadWorkQueue thread;\r
+\r
+ protected IMouseCaptureContext mouseCaptureCtx = new MouseCaptureContext();\r
+ protected IMouseCursorContext mouseCursorCtx = new MouseCursorContext();\r
+\r
+ protected G2DSceneGraph sceneGraph;\r
+ protected G2DParentNode canvasNode = null;\r
+ protected ITooltipProvider tooltip;\r
+\r
+ protected final AtomicBoolean locked = new AtomicBoolean(false);\r
+\r
+ public CanvasContext(IThreadWorkQueue thread)\r
+ {\r
+ this(thread, new G2DSceneGraph());\r
+ }\r
+\r
+ /**\r
+ *\r
+ * @param thread context thread, or null if sync policy not used\r
+ */\r
+ public CanvasContext(IThreadWorkQueue thread, G2DSceneGraph sg)\r
+ {\r
+ super(ICanvasParticipant.class);\r
+ if ( thread == null )\r
+ throw new IllegalArgumentException("null");\r
+\r
+ sceneGraph = sg;\r
+ canvasNode = sg.addNode(SceneGraphConstants.NAVIGATION_NODE_NAME, G2DParentNode.class); // Add dummy parent node\r
+ canvasNode.setLookupId(SceneGraphConstants.NAVIGATION_NODE_NAME);\r
+ DataNode dataNode = sg.addNode(SceneGraphConstants.DATA_NODE_NAME, DataNode.class);\r
+ dataNode.setLookupId(SceneGraphConstants.DATA_NODE_NAME);\r
+\r
+ this.thread = thread;\r
+ eventHandlerStack = new EventHandlerStack(thread);\r
+ eventQueue = new EventQueue(eventHandlerStack);\r
+ hintStack.addHintContext(bottomHintContext, Integer.MIN_VALUE);\r
+\r
+ // Install scene graph as a handler into the canvas context event\r
+ // handler stack.\r
+ eventHandlerStack.add(this.sceneGraph.getEventHandler(), SceneGraphConstants.SCENEGRAPH_EVENT_PRIORITY);\r
+\r
+ this.addContextListener(thread, new IContextListener<ICanvasParticipant>() {\r
+ @Override\r
+ public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {\r
+ item.addedToContext(CanvasContext.this);\r
+ }\r
+ @Override\r
+ public void itemRemoved(IContext<ICanvasParticipant> sender,\r
+ ICanvasParticipant item) {\r
+ item.removedFromContext(CanvasContext.this);\r
+ }\r
+ });\r
+\r
+ eventQueue.addEventCoalesceler(MouseEventCoalescer.INSTANCE);\r
+ // Order event handling if events are added to the queue\r
+ eventQueue.addQueueListener(new IEventQueueListener() {\r
+ @Override\r
+ public void onEventAdded(IEventQueue queue, Event e, int index) {\r
+ asyncHandleEvents();\r
+ }\r
+ @Override\r
+ public void onQueueEmpty(IEventQueue queue) {\r
+ }\r
+ });\r
+ }\r
+\r
+ public IHintStack getHintStack()\r
+ {\r
+ assertNotDisposed();\r
+ return hintStack;\r
+ }\r
+\r
+ private final Runnable eventHandling = new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (!isAlive())\r
+ return;\r
+ eventHandlingOrdered = false;\r
+ eventQueue.handleEvents();\r
+ }\r
+ };\r
+\r
+ synchronized void asyncHandleEvents() {\r
+ if (eventHandlingOrdered) return;\r
+ eventHandlingOrdered = true;\r
+ ThreadUtils.asyncExec(thread, eventHandling);\r
+ }\r
+\r
+ synchronized void syncHandleEvents() {\r
+ if (eventHandlingOrdered) return;\r
+ eventHandlingOrdered = true;\r
+ ThreadUtils.syncExec(thread, eventHandling);\r
+ }\r
+\r
+ @Override\r
+ public G2DSceneGraph getSceneGraph() {\r
+ return sceneGraph;\r
+ }\r
+\r
+ @Override\r
+ public void setCanvasNode(G2DParentNode node) {\r
+ this.canvasNode = node;\r
+ }\r
+\r
+ @Override\r
+ public G2DParentNode getCanvasNode() {\r
+ return canvasNode;\r
+ }\r
+\r
+ @Override\r
+ public IEventHandlerStack getEventHandlerStack() {\r
+ assertNotDisposed();\r
+ return eventHandlerStack;\r
+ }\r
+\r
+ @Override\r
+ public IThreadWorkQueue getThreadAccess() {\r
+ //assertNotDisposed();\r
+ return thread;\r
+ }\r
+\r
+ @Override\r
+ protected void doDispose() {\r
+ ThreadUtils.syncExec(getThreadAccess(), new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ clear();\r
+ bottomHintContext.clearWithoutNotification();\r
+ if (sceneGraph != null) {\r
+ // Makes sure that scene graph nodes free their resources!\r
+ sceneGraph.removeNodes();\r
+ sceneGraph.cleanup();\r
+ sceneGraph = null;\r
+ }\r
+ // HN: added to decrease memory leaks\r
+ eventHandlerStack = null;\r
+ hintStack = null;\r
+ canvasNode.cleanup();\r
+ canvasNode = null;\r
+ eventQueue = null;\r
+ mouseCaptureCtx = null;\r
+ mouseCursorCtx = null;\r
+ paintableCtx = null;\r
+ listeners.clear();\r
+ listeners = null;\r
+ set.clear();\r
+ set = null;\r
+ tooltip = null;\r
+ }\r
+ });\r
+ }\r
+\r
+ @Override\r
+ public IHintContext getDefaultHintContext() {\r
+ return bottomHintContext;\r
+ }\r
+\r
+ @Override\r
+ public IMouseCursorContext getMouseCursorContext() {\r
+ return mouseCursorCtx;\r
+ }\r
+\r
+ @Override\r
+ public void setMouseCursorContext(IMouseCursorContext ctx) {\r
+ this.mouseCursorCtx = ctx;\r
+ }\r
+\r
+ @Override\r
+ public IMouseCaptureContext getMouseCaptureContext() {\r
+ return mouseCaptureCtx;\r
+ }\r
+\r
+ @Override\r
+ public void setMouseCaptureContext(IMouseCaptureContext mctx) {\r
+ this.mouseCaptureCtx = mctx;\r
+ }\r
+ \r
+ public ITooltipProvider getTooltipProvider() {\r
+ return tooltip;\r
+ }\r
+\r
+ public void setTooltipProvider(ITooltipProvider tooltip) {\r
+ this.tooltip = tooltip;\r
+ }\r
+\r
+ @Override\r
+ public IEventQueue getEventQueue() {\r
+ return eventQueue;\r
+ }\r
+\r
+ @Override\r
+ public IContentContext getContentContext() {\r
+ return paintableCtx;\r
+ }\r
+\r
+ @Override\r
+ public void setContentContext(IContentContext ctx) {\r
+ this.paintableCtx = ctx;\r
+ }\r
+\r
+ @Override\r
+ public void setLocked(boolean locked) {\r
+ boolean previous = this.locked.getAndSet(locked);\r
+ if (!locked && previous != locked) {\r
+ // The context was unlocked!\r
+ getContentContext().setDirty();\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public boolean isLocked() {\r
+ return this.locked.get();\r
+ }\r
+\r
+ /**\r
+ * Assert participant dependies are OK\r
+ */\r
+ public void assertParticipantDependencies() {\r
+ for (ICanvasParticipant ctx : toArray())\r
+ if (ctx instanceof AbstractCanvasParticipant) {\r
+ AbstractCanvasParticipant acp = (AbstractCanvasParticipant) ctx;\r
+ if (!acp.depsSatisfied) {\r
+ throw new AssertionError("Participant "+acp+" dependies unsatisfied");\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ if (isDisposed())\r
+ return super.toString();\r
+\r
+ StringBuilder sb = new StringBuilder();\r
+ if (locked.get())\r
+ sb.append("[CanvasContext@" + System.identityHashCode(this) + " is locked]");\r
+ sb.append("Participants:\n");\r
+ sb.append(EString.addPrefix( super.toString(), "\t" ));\r
+// sb.append("\n\nPainter stack:\n");\r
+// sb.append(EString.addPrefix( getPainterStack().toString(), "\t" ));\r
+ sb.append("\n\nEvent handler stack:\n");\r
+ sb.append(EString.addPrefix( getEventHandlerStack().toString(), "\t" ));\r
+ return sb.toString();\r
+ }\r
+}\r