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