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