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.participant;
\r
14 import java.util.HashSet;
\r
15 import java.util.Set;
\r
16 import java.util.concurrent.ScheduledFuture;
\r
17 import java.util.concurrent.TimeUnit;
\r
19 import org.simantics.g2d.canvas.ICanvasContext;
\r
20 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
\r
21 import org.simantics.g2d.diagram.participant.ElementHeartbeater;
\r
22 import org.simantics.g2d.element.handler.Heartbeat;
\r
23 import org.simantics.scenegraph.g2d.events.Event;
\r
24 import org.simantics.scenegraph.g2d.events.TimeEvent;
\r
25 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
\r
26 import org.simantics.utils.datastructures.disposable.DisposeState;
\r
27 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
\r
28 import org.simantics.utils.datastructures.hints.IHintListener;
\r
29 import org.simantics.utils.datastructures.hints.IHintObservable;
\r
30 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
31 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
32 import org.simantics.utils.threads.ThreadUtils;
\r
35 * Time Participant sends time pulses by setting KEY_TIME hint. The value of
\r
36 * KEY_TIME is system current time in milliseconds.
\r
38 * The hint KEY_TIME_PULSE_INTERVAL controls the interval of time pulse events.
\r
40 * Time pulse events can be listened by listening modifications of KEY_TIME
\r
43 * KEY_TIMER_ENABLED controls the enabled state of the timer, if false, no
\r
44 * events will be sent even if there are recipients.
\r
46 * To be able receive timer events, add a handler like this to your canvas
\r
48 * @EventHandler(priority = 0)
\r
49 * public boolean handleTimeEvent(TimeEvent e) {
\r
51 * // Don't eat the event, let others see it too.
\r
54 * When you need to receive time events, inform TimeParticipant by invoking
\r
55 * <code>timeParticipant.registerForEvents(getClass());</code> and when you no
\r
56 * longer need the events, use
\r
57 * <code>timeParticipant.unregisterForEvents(getClass());</code>. This allows
\r
58 * TimeParticipant to optimize its internal behavior.
\r
60 * @author Toni Kalajainen
\r
61 * @author Tuukka Lehtonen
\r
63 * @see ElementHeartbeater
\r
65 * @see Notifications
\r
67 public class TimeParticipant extends AbstractCanvasParticipant {
\r
69 /** Key for global timer enable state */
\r
70 public static final Key KEY_TIMER_ENABLED = new KeyOf(Boolean.class);
\r
72 /** Key for time code */
\r
73 public static final Key KEY_TIME = new KeyOf(Long.class);
\r
75 /** Key for timer interval in milliseconds */
\r
76 public static final Key KEY_TIME_PULSE_INTERVAL = new KeyOf(Long.class);
\r
78 /** Default interval in milliseconds */
\r
79 public static final long DEFAULT_INTERVAL = 100L;
\r
81 ScheduledFuture<?> future = null;
\r
83 Set<Class<?>> eventRecipients = new HashSet<Class<?>>();
\r
85 IHintListener hintChangeListener = new HintListenerAdapter() {
\r
87 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
88 if (key == KEY_TIME_PULSE_INTERVAL || key == KEY_TIMER_ENABLED) {
\r
94 public void registerForEvents(Class<?> clazz) {
\r
95 boolean isEmpty = eventRecipients.isEmpty();
\r
96 boolean added = eventRecipients.add(clazz);
\r
97 if (isEmpty && added)
\r
98 // We've got a recipient, start sending timer events.
\r
102 public void unregisterForEvents(Class<?> clazz) {
\r
103 eventRecipients.remove(clazz);
\r
104 if (eventRecipients.isEmpty()) {
\r
105 // No more event recipients, stop sending timer events.
\r
110 private boolean hasRecipients() {
\r
111 return !eventRecipients.isEmpty();
\r
114 public boolean isTimerEnabled() {
\r
115 return getHint(KEY_TIMER_ENABLED);
\r
118 public void setTimerEnabled(boolean enabled) {
\r
119 setHint(KEY_TIMER_ENABLED, Boolean.valueOf(enabled));
\r
122 public long getInterval()
\r
124 Long interval = getHint(KEY_TIME_PULSE_INTERVAL);
\r
125 if (interval == null)
\r
126 return DEFAULT_INTERVAL;
\r
130 public void setInterval(long interval)
\r
132 setHint(KEY_TIME_PULSE_INTERVAL, interval);
\r
136 public void addedToContext(ICanvasContext ctx) {
\r
137 super.addedToContext(ctx);
\r
139 setHint(KEY_TIMER_ENABLED, Boolean.FALSE);
\r
140 setHint(KEY_TIME_PULSE_INTERVAL, DEFAULT_INTERVAL);
\r
141 ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener);
\r
142 ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener);
\r
147 public void removedFromContext(ICanvasContext ctx) {
\r
148 ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener);
\r
149 ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener);
\r
152 eventRecipients.clear();
\r
154 super.removedFromContext(ctx);
\r
157 private boolean commandInQueue = false;
\r
159 @EventHandler(priority = Integer.MAX_VALUE)
\r
160 public boolean handleTimeEvent(TimeEvent e) {
\r
161 commandInQueue = false;
\r
165 private long prevTime = System.currentTimeMillis();
\r
167 private final Runnable onEvent = new Runnable() {
\r
169 public void run() {
\r
170 if (isRemoved() || !hasRecipients() || !isTimerEnabled()) {
\r
175 if (commandInQueue) return;
\r
176 ICanvasContext ctx = getContext();
\r
177 if (ctx==null || ctx.getDisposeState()!=DisposeState.Alive) return;
\r
178 long pTime = prevTime;
\r
179 Long time = System.currentTimeMillis();
\r
180 long interval = time - pTime;
\r
182 setHint(KEY_TIME, time);
\r
183 Event e = new TimeEvent(getContext(), pTime, interval);
\r
184 commandInQueue = true;
\r
185 //System.out.println("sending time event: " + e);
\r
186 getContext().getEventQueue().queueFirst(e);
\r
190 Runnable onTimer = new Runnable() {
\r
192 public void run() {
\r
194 asyncExec(onEvent);
\r
198 private void updateInterval()
\r
200 // cancel old timer
\r
205 boolean timerEnabled = isTimerEnabled();
\r
209 long interval = getInterval();
\r
210 future = ThreadUtils.getNonBlockingWorkExecutor().scheduleAtFixedRate(onTimer, DEFAULT_INTERVAL, interval, TimeUnit.MILLISECONDS);
\r
213 private void cancelTimer() {
\r
214 if (future != null) {
\r
215 future.cancel(false);
\r