]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/AbstractCanvasParticipant.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / canvas / impl / AbstractCanvasParticipant.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/AbstractCanvasParticipant.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/AbstractCanvasParticipant.java
new file mode 100644 (file)
index 0000000..4de8548
--- /dev/null
@@ -0,0 +1,554 @@
+/*******************************************************************************\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