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