1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.canvas.impl;
14 import java.lang.reflect.Field;
15 import java.util.HashSet;
18 import org.simantics.g2d.canvas.ICanvasContext;
19 import org.simantics.g2d.canvas.ICanvasParticipant;
20 import org.simantics.g2d.canvas.IContentContext;
21 import org.simantics.g2d.canvas.impl.DependencyReflection.ReferenceDefinition;
22 import org.simantics.g2d.canvas.impl.DependencyReflection.ReferenceType;
23 import org.simantics.g2d.canvas.impl.HintReflection.HintListenerDefinition;
24 import org.simantics.g2d.canvas.impl.SGNodeReflection.CanvasSGNodeDefinition;
25 import org.simantics.scenegraph.g2d.events.EventHandlerReflection;
26 import org.simantics.scenegraph.g2d.events.IEventHandler;
27 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandlerDefinition;
28 import org.simantics.utils.datastructures.context.IContext;
29 import org.simantics.utils.datastructures.context.IContextListener;
30 import org.simantics.utils.datastructures.hints.HintContext;
31 import org.simantics.utils.datastructures.hints.IHintContext;
32 import org.simantics.utils.datastructures.hints.IHintStack;
33 import org.simantics.utils.datastructures.hints.IHintContext.Key;
34 import org.simantics.utils.datastructures.prioritystack.IPriorityStack;
35 import org.simantics.utils.threads.IThreadWorkQueue;
36 import org.simantics.utils.threads.ThreadUtils;
40 * AbstractCanvasParticipant is base implementation for canvas participants.
41 * There is an assertion that states AbstractCanvasParticipant can be added
42 * only once to a canvas.
45 * There is convenience mechanism for adding painter and event handler
46 * listeners. Subclasses create listeners with usage of Painter and
47 * EventHandler annotations. Listeners are automatically added and
48 * removed to/from canvas context.
55 * @EventHandler(priority=200)
56 * public boolean handleEvent(Event e) {
60 * @EventHandler(priority=400)
61 * public boolean handleMousePressEvent(MouseButtonPressedEvent e) {
68 * public void initSG(G2DParentNode parent) {
69 * // Insert a node into the scene graph rendered by the canvas
70 * // this participant is attached to and initialize it to render
71 * // something. Later on, to modify what the node will render, just
72 * // update the node's attributes.
73 * node = parent.addNode("myNode", MyNode.class);
74 * node.setZIndex(Integer.MIN_VALUE);
78 * public void cleanup() {
79 * // Remove our node from the scene graph where it belonged.
80 * // The node must not be used after this anymore.
89 * Local fields are automatically assigned with ICanvasParticipant instances if they
90 * are annotated with either <code>@Dependency</code> or <code>@Reference</code> annotation tag.
91 * <code>@Depedency</code> is a requirement, <code>@Reference</code> is optional.
93 * assertDependencies() verifies that dependencies are satisfied.
94 * Local depsSatisfied field is true when dependencies are satisfied.
100 * class MyParticipant implements ICanvasParticipant {
101 * @Reference MouseMonitor mouseMonitor;
102 * @Dependency TimeParticipant timeParticipant;
104 * @Painter(priority=100)
105 * public void paint(GraphicsContext gc) {
106 * assertDependencies(); // timeParticipant != null
108 * timeParticipant.doSomething();
110 * if (mouseMonitor!=null) doSomethingElse();
112 * }</pre></blockquote>
115 * Hint listener annotation.
122 * @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")
123 * public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
126 * @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")
127 * public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
130 * </pre></blockquote>
132 * @author Toni Kalajainen
134 public abstract class AbstractCanvasParticipant implements ICanvasParticipant {
136 /** The interactor/canvas context */
137 private ICanvasContext context;
139 /** The thread used in the context */
140 private IThreadWorkQueue thread;
142 /** the local hint context */
143 protected IHintContext localHintCtx = null;
144 protected int localPriority = 0;
146 /** wrapped local hint context. reads from hint stack */
147 protected IHintContext hintCtx = null;
149 /** Cached hint stack value */
150 protected IHintStack hintStack;
152 /** Painters found with reflection */
153 protected CanvasSGNodeDefinition[] sghandlers;
155 /** Painters found with reflection */
156 protected EventHandlerDefinition[] eventHandlers;
158 protected HintListenerDefinition[] hintListeners;
160 /** Reference definitions */
161 protected ReferenceDefinition[] refDefs;
162 protected boolean depsSatisfied = false;
163 private final IContextListener<ICanvasParticipant> ctxListener =
164 new IContextListener<ICanvasParticipant>() {
166 public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
167 _itemAdded(sender, item);
168 if (!depsSatisfied) depsSatisfied = checkDependencies();
171 public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
172 _itemRemoved(sender, item);
173 AbstractCanvasParticipant.this.depsSatisfied = depsSatisfied;
177 Set<Field> missingDependencies = new HashSet<>();
179 @SuppressWarnings("unchecked")
180 private void _itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item)
182 // This code handles @Dependency annotation
183 // Synchronizing because the calling thread may be anything
184 synchronized ( ctxListener ) {
185 Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();
187 for (ReferenceDefinition def : refDefs)
189 Class<?> defClass = def.requirement;
190 if (!defClass.isAssignableFrom(c)) continue;
192 Object value = f.get(AbstractCanvasParticipant.this);
194 f.set(AbstractCanvasParticipant.this, item);
196 } catch (Exception e) {
197 throw new RuntimeException(e);
201 @SuppressWarnings("unchecked")
202 private void _itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item)
204 synchronized ( ctxListener ) {
205 Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();
206 //boolean depsSatisfied = true;
208 for (ReferenceDefinition def : refDefs)
210 Class<?> defClass = def.requirement;
212 //Object value = f.get(AbstractCanvasParticipant.this);
213 if (defClass.isAssignableFrom(c)) {
215 f.set(AbstractCanvasParticipant.this, null);
217 //depsSatisfied &= !def.dependency || value!=null;
219 } catch (Exception e) {
220 throw new RuntimeException(e);
226 * AbstractInteractor adds itself to the context set in the constructor
229 * The constructor creates hint constructor and adds it into the context
230 * with priority value Integer.MIN_VALUE. Generally, base service interactors
231 * only read hints. Those hints are usually overrideable and thus the default
232 * hint context priority is as small value as possible. Use this constructor for
233 * BASE SERVICE interactors that only read values.
237 public AbstractCanvasParticipant()
239 sghandlers = SGNodeReflection.getSGHandlers(this);
240 eventHandlers = EventHandlerReflection.getEventHandlers(this);
241 refDefs = DependencyReflection.getDependencies(this, ReferenceType.CanvasParticipant);
242 hintListeners = HintReflection.getDependencies(this);
243 if (refDefs.length==0) depsSatisfied = true;
247 public void addedToContext(ICanvasContext ctx)
250 assert(context==null);
252 this.hintStack = ctx.getHintStack();
253 this.thread = ctx.getThreadAccess();
255 // Add event handlers
256 IPriorityStack<IEventHandler> eventHandlerStack = getContext().getEventHandlerStack();
257 for (EventHandlerDefinition eventHandler : eventHandlers)
258 eventHandlerStack.add(eventHandler.eventHandler, eventHandler.priority);
260 // Fill References & Monitor for changes
261 if (refDefs.length!=0) {
262 getContext().addContextListener(ctxListener);
263 for (ICanvasParticipant cp : getContext().toArray())
264 _itemAdded(getContext(), cp);
265 depsSatisfied = checkDependencies();
268 // Add hint reflections
269 IHintStack stack = getContext().getHintStack();
270 IThreadWorkQueue thread = getContext().getThreadAccess();
271 for (HintListenerDefinition def : hintListeners)
272 stack.addKeyHintListener(thread, def.key, def);
275 // Create SceneGraph nodes
276 for (CanvasSGNodeDefinition sg : sghandlers) {
277 if (sg.initDesignation != null) {
278 switch (sg.initDesignation) {
280 sg.init(ctx.getSceneGraph());
283 sg.init(ctx.getCanvasNode());
291 public void removedFromContext(ICanvasContext ctx)
295 throw new RuntimeException("Interactor was not in any context");
297 // Remove context listener
298 if (refDefs.length!=0) {
299 getContext().removeContextListener(ctxListener);
302 // Clean up SceneGraph nodes
303 for (CanvasSGNodeDefinition sg : sghandlers) {
307 IPriorityStack<IEventHandler> eventHandlerStack = context.getEventHandlerStack();
308 for (EventHandlerDefinition eventHandler : eventHandlers)
309 eventHandlerStack.remove(eventHandler.eventHandler);
311 IHintStack stack = getContext().getHintStack();
312 IThreadWorkQueue thread = getContext().getThreadAccess();
313 for (HintListenerDefinition def : hintListeners)
314 stack.removeKeyHintListener(thread, def.key, def);
316 if (localHintCtx!=null) {
317 context.getHintStack().removeHintContext(localHintCtx);
323 * Has this interactor been removed from the context
324 * @return true if interactor has been removed from the context
326 public boolean isRemoved()
328 return context==null;
334 * Removes this interactor from its designed context.
335 * This methods can be invoked only once.
340 throw new RuntimeException("Interactor has already been removed from the context");
341 context.remove(this);
345 * Returns the hint stack of the context.
348 public IHintStack getHintStack()
354 * Get local hint context. This context contains all hints set by this
355 * particular interactor. Modifications affect the hint stack of the
356 * interaction context, although some modifications may not become effective
357 * in case the key is overridden by another hint context in the stack.
359 * Local context must be created with createLocalHintContext() method.
360 * constructor has defualt value Integer.MIN_VALUE.
362 * Reading from this context returns only the local hint and not
363 * from the shared hint stack of the canvas context.
365 * To read from the shared hint stack, use getHint() or getHintStack() instead.
367 * @return the local hint context
369 public synchronized IHintContext getLocalHintContext()
375 * Get hint context. Read operations are to hint stack, writes to local stack.
379 public synchronized IHintContext getWriteableHintStack()
382 hintCtx = getHintStack().createStackRead(getLocalHintContext());
387 * Get hint context; local if exists, otherwise stack's default context.
391 public IHintContext getWriteableHintContext()
393 ICanvasContext ctx = context;
395 if (localHintCtx!=null) return localHintCtx;
396 return ctx.getDefaultHintContext();
400 * Read hint from the hint stack.
405 public <E> E getHint(Key key)
407 return hintStack.getHint(key);
410 public boolean hasHint(Key key)
412 return hintStack.getHint(key)!=null;
416 * Set hint to the local hint stack.
418 * Thread safe - switches to context thread
421 * @param value the value
423 public void setHint(final Key key, final Object value)
425 if (getThread().currentThreadAccess())
426 getWriteableHintContext().setHint(key, value);
428 // syncExec(new Runnable() {
430 // public void run() {
431 // getWriteableHintContext().setHint(key, value);
433 throw new IllegalStateException("illegal thread access, expected " + getThread().getThread() + ", was in "
434 + Thread.currentThread());
439 * Remove hint from local hint context / stack's default context
441 * Thread safe - switches to context thread
445 public void removeHint(final Key key)
447 if (getThread().currentThreadAccess())
448 getWriteableHintContext().removeHint(key);
450 // syncExec(new Runnable() {
452 // public void run() {
453 // getWriteableHintContext().removeHint(key);
455 throw new IllegalStateException("illegal thread access");
459 public void syncExec(Runnable r)
461 ThreadUtils.syncExec(getThread(), r);
464 public void asyncExec(Runnable r)
466 ThreadUtils.asyncExec(getThread(), r);
470 * Create local hint context and add it to the hint stack. It will be
471 * automatically removed when this participant is removed from the canvas.
473 * Local hint context overrides default context in the following convenience methods:
474 * getHint(), setHint(), setHintAsync()
476 * Q: What is the purpose of local hint stack?
477 * A: To override hints for the lifetime of the participant.
478 * For example, Special editing mode overrides viewport and toolmode.
482 public void createLocalHintContext(int priority)
484 localHintCtx = new HintContext();
488 * Set hint to the local hint stack if one exists otherwise to the default context.
489 * Switches thread to the appropriate context thread.
492 * @param value the value
494 public void setHintAsync(final Key key, final Object value)
496 assert(context!=null);
497 asyncExec(new Runnable() {
502 if (localHintCtx!=null)
503 localHintCtx.setHint(key, value);
505 context.getDefaultHintContext().setHint(key, value);
510 * Get the interactor context.
511 * @return the context
513 public ICanvasContext getContext()
518 public IThreadWorkQueue getThread()
525 * Asserts that dependencies of participants are satisfied.
527 public void assertDependencies()
529 assert(depsSatisfied);
532 private boolean checkDependencies() {
533 synchronized ( ctxListener ) {
535 for (ReferenceDefinition rd : refDefs) {
539 Object o = f.get(this);
541 missingDependencies.add(f);
544 missingDependencies.remove(f);
547 } catch (Exception e) {
548 throw new RuntimeException(e);
554 public void setDirty()
556 ICanvasContext ctx = getContext();
557 if ( ctx==null ) return;
558 IContentContext cctx = ctx.getContentContext();
559 if ( cctx==null ) return;