--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.g2d.participant;\r
+\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+import java.util.concurrent.ScheduledFuture;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;\r
+import org.simantics.g2d.diagram.participant.ElementHeartbeater;\r
+import org.simantics.g2d.element.handler.Heartbeat;\r
+import org.simantics.scenegraph.g2d.events.Event;\r
+import org.simantics.scenegraph.g2d.events.TimeEvent;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
+import org.simantics.utils.datastructures.disposable.DisposeState;\r
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
+import org.simantics.utils.datastructures.hints.IHintListener;\r
+import org.simantics.utils.datastructures.hints.IHintObservable;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+\r
+/**\r
+ * Time Participant sends time pulses by setting KEY_TIME hint. The value of\r
+ * KEY_TIME is system current time in milliseconds.\r
+ * \r
+ * The hint KEY_TIME_PULSE_INTERVAL controls the interval of time pulse events.\r
+ * \r
+ * Time pulse events can be listened by listening modifications of KEY_TIME\r
+ * hint.\r
+ * \r
+ * KEY_TIMER_ENABLED controls the enabled state of the timer, if false, no\r
+ * events will be sent even if there are recipients.\r
+ * \r
+ * To be able receive timer events, add a handler like this to your canvas\r
+ * participant:<pre>\r
+ * @EventHandler(priority = 0)\r
+ * public boolean handleTimeEvent(TimeEvent e) {\r
+ * // do something\r
+ * // Don't eat the event, let others see it too.\r
+ * return false;\r
+ * }</pre>\r
+ * When you need to receive time events, inform TimeParticipant by invoking\r
+ * <code>timeParticipant.registerForEvents(getClass());</code> and when you no\r
+ * longer need the events, use\r
+ * <code>timeParticipant.unregisterForEvents(getClass());</code>. This allows\r
+ * TimeParticipant to optimize its internal behavior.\r
+ *\r
+ * @author Toni Kalajainen\r
+ * @author Tuukka Lehtonen\r
+ *\r
+ * @see ElementHeartbeater\r
+ * @see Heartbeat\r
+ * @see Notifications\r
+ */\r
+public class TimeParticipant extends AbstractCanvasParticipant {\r
+\r
+ /** Key for global timer enable state */\r
+ public static final Key KEY_TIMER_ENABLED = new KeyOf(Boolean.class);\r
+\r
+ /** Key for time code */\r
+ public static final Key KEY_TIME = new KeyOf(Long.class);\r
+\r
+ /** Key for timer interval in milliseconds */\r
+ public static final Key KEY_TIME_PULSE_INTERVAL = new KeyOf(Long.class);\r
+\r
+ /** Default interval in milliseconds */\r
+ public static final long DEFAULT_INTERVAL = 100L;\r
+\r
+ ScheduledFuture<?> future = null;\r
+\r
+ Set<Class<?>> eventRecipients = new HashSet<Class<?>>();\r
+\r
+ IHintListener hintChangeListener = new HintListenerAdapter() {\r
+ @Override\r
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
+ if (key == KEY_TIME_PULSE_INTERVAL || key == KEY_TIMER_ENABLED) {\r
+ updateInterval();\r
+ }\r
+ }\r
+ };\r
+\r
+ public void registerForEvents(Class<?> clazz) {\r
+ boolean isEmpty = eventRecipients.isEmpty();\r
+ boolean added = eventRecipients.add(clazz);\r
+ if (isEmpty && added)\r
+ // We've got a recipient, start sending timer events.\r
+ updateInterval();\r
+ }\r
+\r
+ public void unregisterForEvents(Class<?> clazz) {\r
+ eventRecipients.remove(clazz);\r
+ if (eventRecipients.isEmpty()) {\r
+ // No more event recipients, stop sending timer events.\r
+ cancelTimer();\r
+ }\r
+ }\r
+\r
+ private boolean hasRecipients() {\r
+ return !eventRecipients.isEmpty();\r
+ }\r
+\r
+ public boolean isTimerEnabled() {\r
+ return getHint(KEY_TIMER_ENABLED);\r
+ }\r
+\r
+ public void setTimerEnabled(boolean enabled) {\r
+ setHint(KEY_TIMER_ENABLED, Boolean.valueOf(enabled));\r
+ }\r
+\r
+ public long getInterval()\r
+ {\r
+ Long interval = getHint(KEY_TIME_PULSE_INTERVAL);\r
+ if (interval == null)\r
+ return DEFAULT_INTERVAL;\r
+ return interval;\r
+ }\r
+\r
+ public void setInterval(long interval)\r
+ {\r
+ setHint(KEY_TIME_PULSE_INTERVAL, interval);\r
+ }\r
+\r
+ @Override\r
+ public void addedToContext(ICanvasContext ctx) {\r
+ super.addedToContext(ctx);\r
+\r
+ setHint(KEY_TIMER_ENABLED, Boolean.FALSE);\r
+ setHint(KEY_TIME_PULSE_INTERVAL, DEFAULT_INTERVAL);\r
+ ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener);\r
+ ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener);\r
+ updateInterval();\r
+ }\r
+\r
+ @Override\r
+ public void removedFromContext(ICanvasContext ctx) {\r
+ ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener);\r
+ ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener);\r
+ cancelTimer();\r
+\r
+ eventRecipients.clear();\r
+\r
+ super.removedFromContext(ctx);\r
+ }\r
+\r
+ private boolean commandInQueue = false;\r
+\r
+ @EventHandler(priority = Integer.MAX_VALUE)\r
+ public boolean handleTimeEvent(TimeEvent e) {\r
+ commandInQueue = false;\r
+ return false;\r
+ }\r
+\r
+ private long prevTime = System.currentTimeMillis();\r
+\r
+ private final Runnable onEvent = new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (isRemoved() || !hasRecipients() || !isTimerEnabled()) {\r
+ cancelTimer();\r
+ return;\r
+ }\r
+\r
+ if (commandInQueue) return;\r
+ ICanvasContext ctx = getContext();\r
+ if (ctx==null || ctx.getDisposeState()!=DisposeState.Alive) return;\r
+ long pTime = prevTime;\r
+ Long time = System.currentTimeMillis();\r
+ long interval = time - pTime;\r
+ prevTime = time;\r
+ setHint(KEY_TIME, time);\r
+ Event e = new TimeEvent(getContext(), pTime, interval);\r
+ commandInQueue = true;\r
+ //System.out.println("sending time event: " + e);\r
+ getContext().getEventQueue().queueFirst(e);\r
+ }\r
+ };\r
+\r
+ Runnable onTimer = new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ // On time pulse\r
+ asyncExec(onEvent);\r
+ }\r
+ };\r
+\r
+ private void updateInterval()\r
+ {\r
+ // cancel old timer\r
+ cancelTimer();\r
+ if (isRemoved())\r
+ return;\r
+\r
+ boolean timerEnabled = isTimerEnabled();\r
+ if (!timerEnabled)\r
+ return;\r
+\r
+ long interval = getInterval();\r
+ future = ThreadUtils.getNonBlockingWorkExecutor().scheduleAtFixedRate(onTimer, DEFAULT_INTERVAL, interval, TimeUnit.MILLISECONDS);\r
+ }\r
+\r
+ private void cancelTimer() {\r
+ if (future != null) {\r
+ future.cancel(false);\r
+ future = null;\r
+ }\r
+ }\r
+\r
+}\r