X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fcanvas%2Fimpl%2FAbstractCanvasParticipant.java;fp=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fcanvas%2Fimpl%2FAbstractCanvasParticipant.java;h=4de85481281222fc2646c8960bd452f51dffb6f6;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git 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 index 000000000..4de854812 --- /dev/null +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/AbstractCanvasParticipant.java @@ -0,0 +1,554 @@ +/******************************************************************************* + * 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 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. + * + *

+ * 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. + *

+ * + *

+ * Example: + *

+ *
+ *   @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;
+ *       }
+ *   }
+ * 
+ * + *

+ * Local fields are automatically assigned with ICanvasParticipant instances if they + * are annotated with either @Dependency or @Reference annotation tag. + * @Depedency is a requirement, @Reference is optional. + * + * assertDependencies() verifies that dependencies are satisfied. + * Local depsSatisfied field is true when dependencies are satisfied. + * + *

+ * Example: + *

+ *
+ *   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();
+ *       }
+ *   }
+ * + *

+ * Hint listener annotation. + *

+ * + *

+ * Example: + *

+ *
+ *   @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) {
+ *       ...
+ *   }
+ * 
+ * + * @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 ctxListener = + new IContextListener() { + @Override + public void itemAdded(IContext sender, ICanvasParticipant item) { + _itemAdded(sender, item); + if (!depsSatisfied) depsSatisfied = checkDependencies(); + } + @Override + public void itemRemoved(IContext sender, ICanvasParticipant item) { + _itemRemoved(sender, item); + AbstractCanvasParticipant.this.depsSatisfied = depsSatisfied; + } + }; + @SuppressWarnings("unchecked") + private void _itemAdded(IContext sender, ICanvasParticipant item) + { + // This code handles @Dependency annotation + // Synchronizing because the calling thread may be anything + synchronized ( ctxListener ) { + Class c = (Class) 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 sender, ICanvasParticipant item) + { + synchronized ( ctxListener ) { + Class c = (Class) 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 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 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. + *

+ * Local context must be created with createLocalHintContext() method. + * constructor has defualt value Integer.MIN_VALUE. + *

+ * Reading from this context returns only the local hint and not + * from the shared hint stack of the canvas context. + *

+ * 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 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. + *

+ * Local hint context overrides default context in the following convenience methods: + * getHint(), setHint(), setHintAsync() + *

+ * 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) + return false; + } + } 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(); + } + +}