]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/participant/TimeParticipant.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / participant / TimeParticipant.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/TimeParticipant.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/TimeParticipant.java
new file mode 100644 (file)
index 0000000..3b7f5ae
--- /dev/null
@@ -0,0 +1,220 @@
+/*******************************************************************************\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
+ * &#064;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