1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.canvas.impl;
\r
14 import java.lang.reflect.Field;
\r
16 import org.simantics.g2d.canvas.ICanvasContext;
\r
17 import org.simantics.g2d.canvas.ICanvasParticipant;
\r
18 import org.simantics.g2d.canvas.IContentContext;
\r
19 import org.simantics.g2d.canvas.impl.DependencyReflection.ReferenceDefinition;
\r
20 import org.simantics.g2d.canvas.impl.DependencyReflection.ReferenceType;
\r
21 import org.simantics.g2d.canvas.impl.HintReflection.HintListenerDefinition;
\r
22 import org.simantics.g2d.canvas.impl.SGNodeReflection.CanvasSGNodeDefinition;
\r
23 import org.simantics.scenegraph.g2d.events.EventHandlerReflection;
\r
24 import org.simantics.scenegraph.g2d.events.IEventHandler;
\r
25 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandlerDefinition;
\r
26 import org.simantics.utils.datastructures.context.IContext;
\r
27 import org.simantics.utils.datastructures.context.IContextListener;
\r
28 import org.simantics.utils.datastructures.hints.HintContext;
\r
29 import org.simantics.utils.datastructures.hints.IHintContext;
\r
30 import org.simantics.utils.datastructures.hints.IHintStack;
\r
31 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
32 import org.simantics.utils.datastructures.prioritystack.IPriorityStack;
\r
33 import org.simantics.utils.threads.IThreadWorkQueue;
\r
34 import org.simantics.utils.threads.ThreadUtils;
\r
38 * AbstractCanvasParticipant is base implementation for canvas participants.
\r
39 * There is an assertion that states AbstractCanvasParticipant can be added
\r
40 * only once to a canvas.
\r
43 * There is convenience mechanism for adding painter and event handler
\r
44 * listeners. Subclasses create listeners with usage of Painter and
\r
45 * EventHandler annotations. Listeners are automatically added and
\r
46 * removed to/from canvas context.
\r
53 * @EventHandler(priority=200)
\r
54 * public boolean handleEvent(Event e) {
\r
58 * @EventHandler(priority=400)
\r
59 * public boolean handleMousePressEvent(MouseButtonPressedEvent e) {
\r
66 * public void initSG(G2DParentNode parent) {
\r
67 * // Insert a node into the scene graph rendered by the canvas
\r
68 * // this participant is attached to and initialize it to render
\r
69 * // something. Later on, to modify what the node will render, just
\r
70 * // update the node's attributes.
\r
71 * node = parent.addNode("myNode", MyNode.class);
\r
72 * node.setZIndex(Integer.MIN_VALUE);
\r
76 * public void cleanup() {
\r
77 * // Remove our node from the scene graph where it belonged.
\r
78 * // The node must not be used after this anymore.
\r
79 * if (node != null) {
\r
84 * </pre></blockquote>
\r
87 * Local fields are automatically assigned with ICanvasParticipant instances if they
\r
88 * are annotated with either <code>@Dependency</code> or <code>@Reference</code> annotation tag.
\r
89 * <code>@Depedency</code> is a requirement, <code>@Reference</code> is optional.
\r
91 * assertDependencies() verifies that dependencies are satisfied.
\r
92 * Local depsSatisfied field is true when dependencies are satisfied.
\r
98 * class MyParticipant implements ICanvasParticipant {
\r
99 * @Reference MouseMonitor mouseMonitor;
\r
100 * @Dependency TimeParticipant timeParticipant;
\r
102 * @Painter(priority=100)
\r
103 * public void paint(GraphicsContext gc) {
\r
104 * assertDependencies(); // timeParticipant != null
\r
106 * timeParticipant.doSomething();
\r
108 * if (mouseMonitor!=null) doSomethingElse();
\r
110 * }</pre></blockquote>
\r
113 * Hint listener annotation.
\r
119 * <blockquote><pre>
\r
120 * @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")
\r
121 * public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
124 * @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")
\r
125 * public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
\r
128 * </pre></blockquote>
\r
130 * @author Toni Kalajainen
\r
132 public abstract class AbstractCanvasParticipant implements ICanvasParticipant {
\r
134 /** The interactor/canvas context */
\r
135 private ICanvasContext context;
\r
137 /** The thread used in the context */
\r
138 private IThreadWorkQueue thread;
\r
140 /** the local hint context */
\r
141 protected IHintContext localHintCtx = null;
\r
142 protected int localPriority = 0;
\r
144 /** wrapped local hint context. reads from hint stack */
\r
145 protected IHintContext hintCtx = null;
\r
147 /** Cached hint stack value */
\r
148 protected IHintStack hintStack;
\r
150 /** Painters found with reflection */
\r
151 protected CanvasSGNodeDefinition[] sghandlers;
\r
153 /** Painters found with reflection */
\r
154 protected EventHandlerDefinition[] eventHandlers;
\r
156 protected HintListenerDefinition[] hintListeners;
\r
158 /** Reference definitions */
\r
159 protected ReferenceDefinition[] refDefs;
\r
160 protected boolean depsSatisfied = false;
\r
161 private final IContextListener<ICanvasParticipant> ctxListener =
\r
162 new IContextListener<ICanvasParticipant>() {
\r
164 public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
\r
165 _itemAdded(sender, item);
\r
166 if (!depsSatisfied) depsSatisfied = checkDependencies();
\r
169 public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
\r
170 _itemRemoved(sender, item);
\r
171 AbstractCanvasParticipant.this.depsSatisfied = depsSatisfied;
\r
174 @SuppressWarnings("unchecked")
\r
175 private void _itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item)
\r
177 // This code handles @Dependency annotation
\r
178 // Synchronizing because the calling thread may be anything
\r
179 synchronized ( ctxListener ) {
\r
180 Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();
\r
182 for (ReferenceDefinition def : refDefs)
\r
184 Class<?> defClass = def.requirement;
\r
185 if (!defClass.isAssignableFrom(c)) continue;
\r
186 Field f = def.field;
\r
187 Object value = f.get(AbstractCanvasParticipant.this);
\r
188 assert(value==null);
\r
189 f.set(AbstractCanvasParticipant.this, item);
\r
191 } catch (Exception e) {
\r
192 throw new RuntimeException(e);
\r
196 @SuppressWarnings("unchecked")
\r
197 private void _itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item)
\r
199 synchronized ( ctxListener ) {
\r
200 Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();
\r
201 //boolean depsSatisfied = true;
\r
203 for (ReferenceDefinition def : refDefs)
\r
205 Class<?> defClass = def.requirement;
\r
206 Field f = def.field;
\r
207 //Object value = f.get(AbstractCanvasParticipant.this);
\r
208 if (defClass.isAssignableFrom(c)) {
\r
210 f.set(AbstractCanvasParticipant.this, null);
\r
212 //depsSatisfied &= !def.dependency || value!=null;
\r
214 } catch (Exception e) {
\r
215 throw new RuntimeException(e);
\r
221 * AbstractInteractor adds itself to the context set in the constructor
\r
224 * The constructor creates hint constructor and adds it into the context
\r
225 * with priority value Integer.MIN_VALUE. Generally, base service interactors
\r
226 * only read hints. Those hints are usually overrideable and thus the default
\r
227 * hint context priority is as small value as possible. Use this constructor for
\r
228 * BASE SERVICE interactors that only read values.
\r
232 public AbstractCanvasParticipant()
\r
234 sghandlers = SGNodeReflection.getSGHandlers(this);
\r
235 eventHandlers = EventHandlerReflection.getEventHandlers(this);
\r
236 refDefs = DependencyReflection.getDependencies(this, ReferenceType.CanvasParticipant);
\r
237 hintListeners = HintReflection.getDependencies(this);
\r
238 if (refDefs.length==0) depsSatisfied = true;
\r
242 public void addedToContext(ICanvasContext ctx)
\r
245 assert(context==null);
\r
246 this.context = ctx;
\r
247 this.hintStack = ctx.getHintStack();
\r
248 this.thread = ctx.getThreadAccess();
\r
250 // Add event handlers
\r
251 IPriorityStack<IEventHandler> eventHandlerStack = getContext().getEventHandlerStack();
\r
252 for (EventHandlerDefinition eventHandler : eventHandlers)
\r
253 eventHandlerStack.add(eventHandler.eventHandler, eventHandler.priority);
\r
255 // Fill References & Monitor for changes
\r
256 if (refDefs.length!=0) {
\r
257 getContext().addContextListener(ctxListener);
\r
258 for (ICanvasParticipant cp : getContext().toArray())
\r
259 _itemAdded(getContext(), cp);
\r
260 depsSatisfied = checkDependencies();
\r
263 // Add hint reflections
\r
264 IHintStack stack = getContext().getHintStack();
\r
265 IThreadWorkQueue thread = getContext().getThreadAccess();
\r
266 for (HintListenerDefinition def : hintListeners)
\r
267 stack.addKeyHintListener(thread, def.key, def);
\r
270 // Create SceneGraph nodes
\r
271 for (CanvasSGNodeDefinition sg : sghandlers) {
\r
272 if (sg.initDesignation != null) {
\r
273 switch (sg.initDesignation) {
\r
275 sg.init(ctx.getSceneGraph());
\r
278 sg.init(ctx.getCanvasNode());
\r
286 public void removedFromContext(ICanvasContext ctx)
\r
290 throw new RuntimeException("Interactor was not in any context");
\r
292 // Remove context listener
\r
293 if (refDefs.length!=0) {
\r
294 getContext().removeContextListener(ctxListener);
\r
297 // Clean up SceneGraph nodes
\r
298 for (CanvasSGNodeDefinition sg : sghandlers) {
\r
302 IPriorityStack<IEventHandler> eventHandlerStack = context.getEventHandlerStack();
\r
303 for (EventHandlerDefinition eventHandler : eventHandlers)
\r
304 eventHandlerStack.remove(eventHandler.eventHandler);
\r
306 IHintStack stack = getContext().getHintStack();
\r
307 IThreadWorkQueue thread = getContext().getThreadAccess();
\r
308 for (HintListenerDefinition def : hintListeners)
\r
309 stack.removeKeyHintListener(thread, def.key, def);
\r
311 if (localHintCtx!=null) {
\r
312 context.getHintStack().removeHintContext(localHintCtx);
\r
318 * Has this interactor been removed from the context
\r
319 * @return true if interactor has been removed from the context
\r
321 public boolean isRemoved()
\r
323 return context==null;
\r
329 * Removes this interactor from its designed context.
\r
330 * This methods can be invoked only once.
\r
332 public void remove()
\r
335 throw new RuntimeException("Interactor has already been removed from the context");
\r
336 context.remove(this);
\r
340 * Returns the hint stack of the context.
\r
341 * @return hint stack
\r
343 public IHintStack getHintStack()
\r
349 * Get local hint context. This context contains all hints set by this
\r
350 * particular interactor. Modifications affect the hint stack of the
\r
351 * interaction context, although some modifications may not become effective
\r
352 * in case the key is overridden by another hint context in the stack.
\r
354 * Local context must be created with createLocalHintContext() method.
\r
355 * constructor has defualt value Integer.MIN_VALUE.
\r
357 * Reading from this context returns only the local hint and not
\r
358 * from the shared hint stack of the canvas context.
\r
360 * To read from the shared hint stack, use getHint() or getHintStack() instead.
\r
362 * @return the local hint context
\r
364 public synchronized IHintContext getLocalHintContext()
\r
366 return localHintCtx;
\r
370 * Get hint context. Read operations are to hint stack, writes to local stack.
\r
374 public synchronized IHintContext getWriteableHintStack()
\r
377 hintCtx = getHintStack().createStackRead(getLocalHintContext());
\r
382 * Get hint context; local if exists, otherwise stack's default context.
\r
386 public IHintContext getWriteableHintContext()
\r
388 ICanvasContext ctx = context;
\r
390 if (localHintCtx!=null) return localHintCtx;
\r
391 return ctx.getDefaultHintContext();
\r
395 * Read hint from the hint stack.
\r
400 public <E> E getHint(Key key)
\r
402 return hintStack.getHint(key);
\r
405 public boolean hasHint(Key key)
\r
407 return hintStack.getHint(key)!=null;
\r
411 * Set hint to the local hint stack.
\r
413 * Thread safe - switches to context thread
\r
415 * @param key the key
\r
416 * @param value the value
\r
418 public void setHint(final Key key, final Object value)
\r
420 if (getThread().currentThreadAccess())
\r
421 getWriteableHintContext().setHint(key, value);
\r
423 // syncExec(new Runnable() {
\r
425 // public void run() {
\r
426 // getWriteableHintContext().setHint(key, value);
\r
428 throw new IllegalStateException("illegal thread access, expected " + getThread().getThread() + ", was in "
\r
429 + Thread.currentThread());
\r
434 * Remove hint from local hint context / stack's default context
\r
436 * Thread safe - switches to context thread
\r
438 * @param key the key
\r
440 public void removeHint(final Key key)
\r
442 if (getThread().currentThreadAccess())
\r
443 getWriteableHintContext().removeHint(key);
\r
445 // syncExec(new Runnable() {
\r
447 // public void run() {
\r
448 // getWriteableHintContext().removeHint(key);
\r
450 throw new IllegalStateException("illegal thread access");
\r
454 public void syncExec(Runnable r)
\r
456 ThreadUtils.syncExec(getThread(), r);
\r
459 public void asyncExec(Runnable r)
\r
461 ThreadUtils.asyncExec(getThread(), r);
\r
465 * Create local hint context and add it to the hint stack. It will be
\r
466 * automatically removed when this participant is removed from the canvas.
\r
468 * Local hint context overrides default context in the following convenience methods:
\r
469 * getHint(), setHint(), setHintAsync()
\r
471 * Q: What is the purpose of local hint stack?
\r
472 * A: To override hints for the lifetime of the participant.
\r
473 * For example, Special editing mode overrides viewport and toolmode.
\r
477 public void createLocalHintContext(int priority)
\r
479 localHintCtx = new HintContext();
\r
483 * Set hint to the local hint stack if one exists otherwise to the default context.
\r
484 * Switches thread to the appropriate context thread.
\r
486 * @param key the key
\r
487 * @param value the value
\r
489 public void setHintAsync(final Key key, final Object value)
\r
491 assert(context!=null);
\r
492 asyncExec(new Runnable() {
\r
494 public void run() {
\r
497 if (localHintCtx!=null)
\r
498 localHintCtx.setHint(key, value);
\r
500 context.getDefaultHintContext().setHint(key, value);
\r
505 * Get the interactor context.
\r
506 * @return the context
\r
508 public ICanvasContext getContext()
\r
513 public IThreadWorkQueue getThread()
\r
520 * Asserts that dependencies of participants are satisfied.
\r
522 public void assertDependencies()
\r
524 assert(depsSatisfied);
\r
527 private boolean checkDependencies() {
\r
528 synchronized ( ctxListener ) {
\r
530 for (ReferenceDefinition rd : refDefs) {
\r
531 if (!rd.dependency)
\r
533 Field f = rd.field;
\r
534 Object o = f.get(this);
\r
538 } catch (Exception e) {
\r
539 throw new RuntimeException(e);
\r
545 public void setDirty()
\r
547 ICanvasContext ctx = getContext();
\r
548 if ( ctx==null ) return;
\r
549 IContentContext cctx = ctx.getContentContext();
\r
550 if ( cctx==null ) return;
\r