--- /dev/null
+/*******************************************************************************\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.lang.reflect.Field;\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.impl.DependencyReflection.ReferenceDefinition;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.ReferenceType;\r
+import org.simantics.g2d.canvas.impl.HintReflection.HintListenerDefinition;\r
+import org.simantics.g2d.canvas.impl.SGNodeReflection.CanvasSGNodeDefinition;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection;\r
+import org.simantics.scenegraph.g2d.events.IEventHandler;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandlerDefinition;\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.IHintContext;\r
+import org.simantics.utils.datastructures.hints.IHintStack;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.prioritystack.IPriorityStack;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+\r
+\r
+/**\r
+ * AbstractCanvasParticipant is base implementation for canvas participants.\r
+ * There is an assertion that states AbstractCanvasParticipant can be added\r
+ * only once to a canvas.\r
+ * \r
+ * <p>\r
+ * There is convenience mechanism for adding painter and event handler\r
+ * listeners. Subclasses create listeners with usage of Painter and\r
+ * EventHandler annotations. Listeners are automatically added and\r
+ * removed to/from canvas context.\r
+ * </p>\r
+ * \r
+ * <p>\r
+ * Example:\r
+ * </p>\r
+ * <blockquote><pre>\r
+ * @EventHandler(priority=200)\r
+ * public boolean handleEvent(Event e) {\r
+ * return false;\r
+ * }\r
+ * \r
+ * @EventHandler(priority=400)\r
+ * public boolean handleMousePressEvent(MouseButtonPressedEvent e) {\r
+ * return false;\r
+ * }\r
+ * \r
+ * IG2DNode node;\r
+ * \r
+ * @SGInit\r
+ * public void initSG(G2DParentNode parent) {\r
+ * // Insert a node into the scene graph rendered by the canvas\r
+ * // this participant is attached to and initialize it to render\r
+ * // something. Later on, to modify what the node will render, just\r
+ * // update the node's attributes.\r
+ * node = parent.addNode("myNode", MyNode.class);\r
+ * node.setZIndex(Integer.MIN_VALUE);\r
+ * }\r
+ * \r
+ * @SGCleanup\r
+ * public void cleanup() {\r
+ * // Remove our node from the scene graph where it belonged.\r
+ * // The node must not be used after this anymore.\r
+ * if (node != null) {\r
+ * node.remove();\r
+ * node = null;\r
+ * }\r
+ * }\r
+ * </pre></blockquote>\r
+ * \r
+ * <p>\r
+ * Local fields are automatically assigned with ICanvasParticipant instances if they\r
+ * are annotated with either <code>@Dependency</code> or <code>@Reference</code> annotation tag.\r
+ * <code>@Depedency</code> is a requirement, <code>@Reference</code> is optional.\r
+ * \r
+ * assertDependencies() verifies that dependencies are satisfied.\r
+ * Local depsSatisfied field is true when dependencies are satisfied.\r
+ * \r
+ * <p>\r
+ * Example:\r
+ * </p>\r
+ * <blockquote><pre>\r
+ * class MyParticipant implements ICanvasParticipant {\r
+ * @Reference MouseMonitor mouseMonitor;\r
+ * @Dependency TimeParticipant timeParticipant;\r
+ *\r
+ * @Painter(priority=100)\r
+ * public void paint(GraphicsContext gc) {\r
+ * assertDependencies(); // timeParticipant != null\r
+ * \r
+ * timeParticipant.doSomething();\r
+ * \r
+ * if (mouseMonitor!=null) doSomethingElse();\r
+ * }\r
+ * }</pre></blockquote>\r
+ * \r
+ * <p>\r
+ * Hint listener annotation.\r
+ * </p>\r
+ * \r
+ * <p>\r
+ * Example:\r
+ * </p>\r
+ * <blockquote><pre>\r
+ * @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")\r
+ * public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
+ * ...\r
+ * }\r
+ * @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")\r
+ * public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {\r
+ * ...\r
+ * }\r
+ * </pre></blockquote>\r
+ * \r
+ * @author Toni Kalajainen\r
+ */\r
+public abstract class AbstractCanvasParticipant implements ICanvasParticipant {\r
+\r
+ /** The interactor/canvas context */\r
+ private ICanvasContext context;\r
+\r
+ /** The thread used in the context */\r
+ private IThreadWorkQueue thread;\r
+\r
+ /** the local hint context */\r
+ protected IHintContext localHintCtx = null;\r
+ protected int localPriority = 0;\r
+\r
+ /** wrapped local hint context. reads from hint stack */\r
+ protected IHintContext hintCtx = null;\r
+\r
+ /** Cached hint stack value */\r
+ protected IHintStack hintStack;\r
+\r
+ /** Painters found with reflection */\r
+ protected CanvasSGNodeDefinition[] sghandlers;\r
+\r
+ /** Painters found with reflection */\r
+ protected EventHandlerDefinition[] eventHandlers;\r
+\r
+ protected HintListenerDefinition[] hintListeners;\r
+\r
+ /** Reference definitions */\r
+ protected ReferenceDefinition[] refDefs;\r
+ protected boolean depsSatisfied = false;\r
+ private final IContextListener<ICanvasParticipant> ctxListener =\r
+ new IContextListener<ICanvasParticipant>() {\r
+ @Override\r
+ public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {\r
+ _itemAdded(sender, item);\r
+ if (!depsSatisfied) depsSatisfied = checkDependencies();\r
+ }\r
+ @Override\r
+ public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {\r
+ _itemRemoved(sender, item);\r
+ AbstractCanvasParticipant.this.depsSatisfied = depsSatisfied;\r
+ }\r
+ };\r
+ @SuppressWarnings("unchecked")\r
+ private void _itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item)\r
+ {\r
+ // This code handles @Dependency annotation\r
+ // Synchronizing because the calling thread may be anything\r
+ synchronized ( ctxListener ) {\r
+ Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();\r
+ try {\r
+ for (ReferenceDefinition def : refDefs)\r
+ {\r
+ Class<?> defClass = def.requirement;\r
+ if (!defClass.isAssignableFrom(c)) continue;\r
+ Field f = def.field;\r
+ Object value = f.get(AbstractCanvasParticipant.this);\r
+ assert(value==null);\r
+ f.set(AbstractCanvasParticipant.this, item);\r
+ }\r
+ } catch (Exception e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ }\r
+ }\r
+ @SuppressWarnings("unchecked")\r
+ private void _itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item)\r
+ {\r
+ synchronized ( ctxListener ) { \r
+ Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();\r
+ //boolean depsSatisfied = true;\r
+ try {\r
+ for (ReferenceDefinition def : refDefs)\r
+ {\r
+ Class<?> defClass = def.requirement;\r
+ Field f = def.field;\r
+ //Object value = f.get(AbstractCanvasParticipant.this);\r
+ if (defClass.isAssignableFrom(c)) {\r
+ //value = null;\r
+ f.set(AbstractCanvasParticipant.this, null);\r
+ }\r
+ //depsSatisfied &= !def.dependency || value!=null;\r
+ }\r
+ } catch (Exception e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * AbstractInteractor adds itself to the context set in the constructor\r
+ * argument.\r
+ * \r
+ * The constructor creates hint constructor and adds it into the context\r
+ * with priority value Integer.MIN_VALUE. Generally, base service interactors\r
+ * only read hints. Those hints are usually overrideable and thus the default\r
+ * hint context priority is as small value as possible. Use this constructor for\r
+ * BASE SERVICE interactors that only read values.\r
+ * \r
+ * @param ctx\r
+ */\r
+ public AbstractCanvasParticipant()\r
+ {\r
+ sghandlers = SGNodeReflection.getSGHandlers(this);\r
+ eventHandlers = EventHandlerReflection.getEventHandlers(this);\r
+ refDefs = DependencyReflection.getDependencies(this, ReferenceType.CanvasParticipant);\r
+ hintListeners = HintReflection.getDependencies(this);\r
+ if (refDefs.length==0) depsSatisfied = true;\r
+ }\r
+\r
+ @Override\r
+ public void addedToContext(ICanvasContext ctx)\r
+ {\r
+ assert(ctx!=null);\r
+ assert(context==null);\r
+ this.context = ctx;\r
+ this.hintStack = ctx.getHintStack();\r
+ this.thread = ctx.getThreadAccess();\r
+\r
+ // Add event handlers\r
+ IPriorityStack<IEventHandler> eventHandlerStack = getContext().getEventHandlerStack();\r
+ for (EventHandlerDefinition eventHandler : eventHandlers)\r
+ eventHandlerStack.add(eventHandler.eventHandler, eventHandler.priority);\r
+\r
+ // Fill References & Monitor for changes\r
+ if (refDefs.length!=0) {\r
+ getContext().addContextListener(ctxListener);\r
+ for (ICanvasParticipant cp : getContext().toArray())\r
+ _itemAdded(getContext(), cp);\r
+ depsSatisfied = checkDependencies();\r
+ }\r
+\r
+ // Add hint reflections\r
+ IHintStack stack = getContext().getHintStack();\r
+ IThreadWorkQueue thread = getContext().getThreadAccess();\r
+ for (HintListenerDefinition def : hintListeners)\r
+ stack.addKeyHintListener(thread, def.key, def);\r
+\r
+\r
+ // Create SceneGraph nodes\r
+ for (CanvasSGNodeDefinition sg : sghandlers) {\r
+ if (sg.initDesignation != null) {\r
+ switch (sg.initDesignation) {\r
+ case CONTROL:\r
+ sg.init(ctx.getSceneGraph());\r
+ break;\r
+ case CANVAS:\r
+ sg.init(ctx.getCanvasNode());\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void removedFromContext(ICanvasContext ctx)\r
+ {\r
+ assert(ctx!=null);\r
+ if (context==null)\r
+ throw new RuntimeException("Interactor was not in any context");\r
+\r
+ // Remove context listener\r
+ if (refDefs.length!=0) {\r
+ getContext().removeContextListener(ctxListener);\r
+ }\r
+\r
+ // Clean up SceneGraph nodes\r
+ for (CanvasSGNodeDefinition sg : sghandlers) {\r
+ sg.cleanup();\r
+ }\r
+\r
+ IPriorityStack<IEventHandler> eventHandlerStack = context.getEventHandlerStack();\r
+ for (EventHandlerDefinition eventHandler : eventHandlers)\r
+ eventHandlerStack.remove(eventHandler.eventHandler);\r
+\r
+ IHintStack stack = getContext().getHintStack();\r
+ IThreadWorkQueue thread = getContext().getThreadAccess();\r
+ for (HintListenerDefinition def : hintListeners)\r
+ stack.removeKeyHintListener(thread, def.key, def);\r
+\r
+ if (localHintCtx!=null) {\r
+ context.getHintStack().removeHintContext(localHintCtx);\r
+ }\r
+ context = null;\r
+ }\r
+\r
+ /**\r
+ * Has this interactor been removed from the context\r
+ * @return true if interactor has been removed from the context\r
+ */\r
+ public boolean isRemoved()\r
+ {\r
+ return context==null;\r
+ }\r
+\r
+ /**\r
+ * Remove Self.\r
+ * \r
+ * Removes this interactor from its designed context.\r
+ * This methods can be invoked only once.\r
+ */\r
+ public void remove()\r
+ {\r
+ if (isRemoved())\r
+ throw new RuntimeException("Interactor has already been removed from the context");\r
+ context.remove(this);\r
+ }\r
+\r
+ /**\r
+ * Returns the hint stack of the context.\r
+ * @return hint stack\r
+ */\r
+ public IHintStack getHintStack()\r
+ {\r
+ return hintStack;\r
+ }\r
+\r
+ /**\r
+ * Get local hint context. This context contains all hints set by this\r
+ * particular interactor. Modifications affect the hint stack of the\r
+ * interaction context, although some modifications may not become effective\r
+ * in case the key is overridden by another hint context in the stack.\r
+ * <p>\r
+ * Local context must be created with createLocalHintContext() method.\r
+ * constructor has defualt value Integer.MIN_VALUE.\r
+ * <p>\r
+ * Reading from this context returns only the local hint and not\r
+ * from the shared hint stack of the canvas context.\r
+ * <p>\r
+ * To read from the shared hint stack, use getHint() or getHintStack() instead.\r
+ * \r
+ * @return the local hint context\r
+ */\r
+ public synchronized IHintContext getLocalHintContext()\r
+ {\r
+ return localHintCtx;\r
+ }\r
+\r
+ /**\r
+ * Get hint context. Read operations are to hint stack, writes to local stack.\r
+ * \r
+ * @return\r
+ */\r
+ public synchronized IHintContext getWriteableHintStack()\r
+ {\r
+ if (hintCtx==null)\r
+ hintCtx = getHintStack().createStackRead(getLocalHintContext());\r
+ return hintCtx;\r
+ }\r
+\r
+ /**\r
+ * Get hint context; local if exists, otherwise stack's default context.\r
+ * \r
+ * @return\r
+ */\r
+ public IHintContext getWriteableHintContext()\r
+ {\r
+ ICanvasContext ctx = context;\r
+ assert(ctx!=null);\r
+ if (localHintCtx!=null) return localHintCtx;\r
+ return ctx.getDefaultHintContext();\r
+ }\r
+\r
+ /**\r
+ * Read hint from the hint stack.\r
+ * \r
+ * @param key\r
+ * @return\r
+ */\r
+ public <E> E getHint(Key key)\r
+ {\r
+ return hintStack.getHint(key);\r
+ }\r
+\r
+ public boolean hasHint(Key key)\r
+ {\r
+ return hintStack.getHint(key)!=null;\r
+ }\r
+\r
+ /**\r
+ * Set hint to the local hint stack.\r
+ * \r
+ * Thread safe - switches to context thread\r
+ * \r
+ * @param key the key\r
+ * @param value the value\r
+ */\r
+ public void setHint(final Key key, final Object value)\r
+ {\r
+ if (getThread().currentThreadAccess())\r
+ getWriteableHintContext().setHint(key, value);\r
+ else {\r
+// syncExec(new Runnable() {\r
+// @Override\r
+// public void run() {\r
+// getWriteableHintContext().setHint(key, value);\r
+// }});\r
+ throw new IllegalStateException("illegal thread access, expected " + getThread().getThread() + ", was in "\r
+ + Thread.currentThread());\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Remove hint from local hint context / stack's default context\r
+ * \r
+ * Thread safe - switches to context thread\r
+ * \r
+ * @param key the key\r
+ */\r
+ public void removeHint(final Key key)\r
+ {\r
+ if (getThread().currentThreadAccess())\r
+ getWriteableHintContext().removeHint(key);\r
+ else\r
+// syncExec(new Runnable() {\r
+// @Override\r
+// public void run() {\r
+// getWriteableHintContext().removeHint(key);\r
+// }});\r
+ throw new IllegalStateException("illegal thread access");\r
+ }\r
+\r
+\r
+ public void syncExec(Runnable r)\r
+ {\r
+ ThreadUtils.syncExec(getThread(), r);\r
+ }\r
+\r
+ public void asyncExec(Runnable r)\r
+ {\r
+ ThreadUtils.asyncExec(getThread(), r);\r
+ }\r
+\r
+ /**\r
+ * Create local hint context and add it to the hint stack. It will be\r
+ * automatically removed when this participant is removed from the canvas.\r
+ * <p>\r
+ * Local hint context overrides default context in the following convenience methods:\r
+ * getHint(), setHint(), setHintAsync()\r
+ * <p>\r
+ * Q: What is the purpose of local hint stack?\r
+ * A: To override hints for the lifetime of the participant.\r
+ * For example, Special editing mode overrides viewport and toolmode.\r
+ * \r
+ * @param priority\r
+ */\r
+ public void createLocalHintContext(int priority)\r
+ {\r
+ localHintCtx = new HintContext();\r
+ }\r
+\r
+ /**\r
+ * Set hint to the local hint stack if one exists otherwise to the default context.\r
+ * Switches thread to the appropriate context thread.\r
+ * \r
+ * @param key the key\r
+ * @param value the value\r
+ */\r
+ public void setHintAsync(final Key key, final Object value)\r
+ {\r
+ assert(context!=null);\r
+ asyncExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (isRemoved())\r
+ return;\r
+ if (localHintCtx!=null)\r
+ localHintCtx.setHint(key, value);\r
+ else\r
+ context.getDefaultHintContext().setHint(key, value);\r
+ }});\r
+ }\r
+\r
+ /**\r
+ * Get the interactor context.\r
+ * @return the context\r
+ */\r
+ public ICanvasContext getContext()\r
+ {\r
+ return context;\r
+ }\r
+\r
+ public IThreadWorkQueue getThread()\r
+ {\r
+ return thread;\r
+ }\r
+\r
+\r
+ /**\r
+ * Asserts that dependencies of participants are satisfied.\r
+ */\r
+ public void assertDependencies()\r
+ {\r
+ assert(depsSatisfied);\r
+ }\r
+\r
+ private boolean checkDependencies() {\r
+ synchronized ( ctxListener ) { \r
+ try {\r
+ for (ReferenceDefinition rd : refDefs) {\r
+ if (!rd.dependency)\r
+ continue;\r
+ Field f = rd.field;\r
+ Object o = f.get(this);\r
+ if (o == null)\r
+ return false;\r
+ }\r
+ } catch (Exception e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ return true;\r
+ }\r
+ }\r
+\r
+ public void setDirty()\r
+ {\r
+ ICanvasContext ctx = getContext();\r
+ if ( ctx==null ) return;\r
+ IContentContext cctx = ctx.getContentContext();\r
+ if ( cctx==null ) return;\r
+ cctx.setDirty();\r
+ }\r
+\r
+}\r