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