]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.simulation.ui/src/org/simantics/simulation/ui/handlers/e4/TimerContribution.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.simulation.ui / src / org / simantics / simulation / ui / handlers / e4 / TimerContribution.java
index 848be40a8e59c1bdbddcaafd098f682b300b3d06..a5f3fa951198c2720dc7ed53450504d3a2a991cc 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2015, 2016 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
- *     Semantum Oy - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.simulation.ui.handlers.e4;\r
-\r
-import java.util.concurrent.ScheduledFuture;\r
-import java.util.concurrent.TimeUnit;\r
-\r
-import javax.annotation.PostConstruct;\r
-import javax.annotation.PreDestroy;\r
-import javax.inject.Inject;\r
-\r
-import org.eclipse.core.runtime.preferences.IEclipsePreferences;\r
-import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;\r
-import org.eclipse.e4.core.di.annotations.Optional;\r
-import org.eclipse.e4.core.di.extensions.Preference;\r
-import org.eclipse.e4.ui.di.UISynchronize;\r
-import org.eclipse.e4.ui.model.application.ui.menu.MToolControl;\r
-import org.eclipse.jface.resource.ColorDescriptor;\r
-import org.eclipse.jface.resource.JFaceResources;\r
-import org.eclipse.jface.resource.LocalResourceManager;\r
-import org.eclipse.jface.resource.ResourceManager;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.graphics.Color;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.graphics.RGB;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Listener;\r
-import org.eclipse.swt.widgets.Text;\r
-import org.simantics.databoard.util.ObjectUtils;\r
-import org.simantics.project.IProject;\r
-import org.simantics.simulation.experiment.ExperimentState;\r
-import org.simantics.simulation.experiment.IDynamicExperiment;\r
-import org.simantics.simulation.experiment.IDynamicExperimentListener;\r
-import org.simantics.simulation.experiment.IExperiment;\r
-import org.simantics.simulation.experiment.IExperimentListener;\r
-import org.simantics.simulation.experiment.SimulationTimeUtil;\r
-import org.simantics.simulation.project.IExperimentManager;\r
-import org.simantics.simulation.project.IExperimentManagerListener;\r
-import org.simantics.ui.SimanticsUI;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-\r
-/**\r
- * E4-version of the old\r
- * {@link org.simantics.simulation.ui.handlers.TimerContribution}.\r
- * \r
- * <p>\r
- * Bound to org.simantics.chart/chart.timeformat preference for the used time\r
- * format. This is not the nicest of solutions since it makes the\r
- * <code>org.simantics.simulation.ui</code> plug-in depend on the\r
- * <code>org.simantics.charts</code> plug-in. However the binding is optional\r
- * and therefore the thin dependency is acceptable for now.\r
- * \r
- * @author Jani Simomaa / Semantum Oy\r
- * @author Tuukka Lehtonen / Semantum Oy\r
- * @since 1.22\r
- */\r
-public class TimerContribution {\r
-\r
-    private static final String PREF_CHART_BUNDLE_ID = "org.simantics.charts";\r
-    private static final String PREF_CHART_TIMEFORMAT = "chart.timeformat";\r
-\r
-    private static final long LABEL_UPDATE_MIN_PERIOD_MS = 100;\r
-\r
-    enum Mode {\r
-        HMS,\r
-        SECONDS;\r
-        Mode next() {\r
-            switch (this) {\r
-                case HMS: return SECONDS;\r
-                case SECONDS: return HMS;\r
-                default: return HMS;\r
-            }\r
-        }\r
-    }\r
-\r
-    boolean              disposed = false;\r
-    Text                 label;\r
-    Point                size;\r
-    double               time     = 0.0;\r
-    private Mode         mode     = Mode.HMS;\r
-\r
-    private IExperimentManager experimentManager;\r
-    private IExperimentManagerListener experimentManagerListener;\r
-    private ExperimentState currentState;\r
-\r
-    private ResourceManager resourceManager;\r
-\r
-    @Inject\r
-    private UISynchronize uisync;\r
-\r
-    /**\r
-     * For listening to the current chart time format preference.\r
-     */\r
-    @Inject\r
-    @Optional\r
-    @Preference(nodePath = PREF_CHART_BUNDLE_ID)\r
-    private IEclipsePreferences chartPreferences;\r
-\r
-    private static String toTimeFormatPreference(Mode mode) {\r
-        switch (mode) {\r
-        case SECONDS: return "Decimal";\r
-        case HMS:\r
-        default: return "Time";\r
-        }\r
-    }\r
-\r
-    private static Mode toMode(String timeFormat) {\r
-        if (timeFormat == null)\r
-            return Mode.HMS;\r
-        switch (timeFormat) {\r
-        case "Decimal": return Mode.SECONDS;\r
-        case "Time":\r
-        default: return Mode.HMS;\r
-        }\r
-    }\r
-\r
-    private IPreferenceChangeListener chartTimeFormatListener = event -> {\r
-        if (PREF_CHART_TIMEFORMAT.equals(event.getKey())) {\r
-            Mode newMode = toMode((String) event.getNewValue());\r
-            if (newMode != mode) {\r
-                mode = newMode;\r
-                uisync.asyncExec(() -> {\r
-                    if (!label.isDisposed()) {\r
-                        updateLabel();\r
-                        updateTooltip();\r
-                    }\r
-                });\r
-            }\r
-        }\r
-    };\r
-\r
-    private static ColorDescriptor     RUNNING_BG = ColorDescriptor.createFrom(new RGB(0, 128, 0));\r
-    private static ColorDescriptor     RUNNING_FG = ColorDescriptor.createFrom(new RGB(255, 255, 255));\r
-\r
-    @PostConstruct\r
-    public void createControls(Composite parent, MToolControl toolControl) {\r
-        IProject project = SimanticsUI.peekProject();\r
-        if (project == null)\r
-            return;\r
-\r
-        IExperimentManager manager = project.getHint(IExperimentManager.KEY_EXPERIMENT_MANAGER);\r
-        if(manager == null)\r
-            return;\r
-\r
-        label = new Text(parent, SWT.BORDER | SWT.CENTER | SWT.READ_ONLY);\r
-        label.setEnabled(false);\r
-        label.setText(getTime());\r
-        label.setToolTipText("Simulation Timer");\r
-\r
-        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), label);\r
-\r
-        updateTooltip();\r
-\r
-        Listener labelListener = new Listener() {\r
-            boolean pressed = false;\r
-            boolean inside = false;\r
-            @Override\r
-            public void handleEvent(Event event) {\r
-                switch (event.type) {\r
-                    case SWT.MouseDown:\r
-                        if (inside && (event.button == 1 || event.button == 2))\r
-                            pressed = true;\r
-                        break;\r
-                    case SWT.MouseUp:\r
-                        if (pressed && inside) {\r
-                            pressed = false;\r
-                            toggleMode();\r
-                        }\r
-                        break;\r
-                    case SWT.MouseEnter:\r
-                        inside = true;\r
-                        break;\r
-                    case SWT.MouseExit:\r
-                        inside = false;\r
-                        break;\r
-                }\r
-            }\r
-        };\r
-        label.addListener(SWT.MouseDown, labelListener);\r
-        label.addListener(SWT.MouseEnter, labelListener);\r
-        label.addListener(SWT.MouseExit, labelListener);\r
-        label.addListener(SWT.MouseUp, labelListener);\r
-\r
-        size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);\r
-        if (currentState != null)\r
-            setLabelVisualsByState(currentState);\r
-\r
-        attachToExperimentManager(manager);\r
-\r
-        if (chartPreferences != null) {\r
-            chartPreferences.addPreferenceChangeListener(chartTimeFormatListener);\r
-            mode = toMode((String) chartPreferences.get(PREF_CHART_TIMEFORMAT, null));\r
-        }\r
-    }\r
-\r
-    @PreDestroy\r
-    public void destroy() {\r
-        if (chartPreferences != null) {\r
-            chartPreferences.removePreferenceChangeListener(chartTimeFormatListener);\r
-        }\r
-    }\r
-\r
-    private void attachToExperimentManager(final IExperimentManager manager) {\r
-        if (experimentManager != null) {\r
-            if (experimentManager.equals(manager))\r
-                return;\r
-            experimentManager.removeListener(experimentManagerListener);\r
-        }\r
-        if (manager == null)\r
-            return;\r
-\r
-        //System.out.println(this + "(" + System.identityHashCode(this) + ") ATTACH TO EXPERIMENT MANAGER " + manager);\r
-\r
-        experimentManagerListener = new IExperimentManagerListener() {\r
-            IDynamicExperiment currentExperiment;\r
-            IExperimentListener currentListener;\r
-\r
-            @Override\r
-            public void managerDisposed() {\r
-                manager.removeListener(this);\r
-            }\r
-            @Override\r
-            public void activeExperimentUnloaded() {\r
-                attachToExperiment(null);\r
-            }\r
-            @Override\r
-            public void activeExperimentLoaded(IExperiment experiment) {\r
-                attachToExperiment(experiment);\r
-            }\r
-            synchronized void attachToExperiment(final IExperiment experiment) {\r
-                if (currentExperiment != null) {\r
-                    currentExperiment.removeListener(currentListener);\r
-                    currentExperiment = null;\r
-                    currentListener = null;\r
-                }\r
-\r
-                if (!(experiment instanceof IDynamicExperiment)) {\r
-                    // Ensure that the timer text value is reset to zero.\r
-                    time = 0;\r
-                    uisync.asyncExec(() -> {\r
-                        if (!label.isDisposed()) {\r
-                            updateLabel();\r
-                            setLabelVisualsByState(ExperimentState.DISPOSED);\r
-                        }\r
-                    });\r
-                    return;\r
-                }\r
-\r
-                IDynamicExperiment dynExp = (IDynamicExperiment) experiment;\r
-                //System.out.println(TimerContribution.this + "(" + System.identityHashCode(TimerContribution.this) + ") ATTACH TO EXPERIMENT " + dynExp);\r
-\r
-                IDynamicExperimentListener listener = new IDynamicExperimentListener() {\r
-                    final IExperimentListener _this = this;\r
-                    long lastUpdateTime = 0;\r
-                    ScheduledFuture<?> timedUpdate = null;\r
-                    ExperimentState lastState = null;\r
-                    @Override\r
-                    public void timeChanged(double newTime) {\r
-                        //System.out.println(this + ".timeChanged: " + newTime);\r
-                        time = newTime;\r
-\r
-                        ScheduledFuture<?> f = timedUpdate;\r
-                        if (f != null && !f.isDone())\r
-                            return;\r
-\r
-                        long timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateTime;\r
-\r
-                        if (timeSinceLastUpdate > LABEL_UPDATE_MIN_PERIOD_MS) {\r
-                            scheduleLabelUpdate();\r
-                        } else {\r
-                            timedUpdate = ThreadUtils.getNonBlockingWorkExecutor().schedule(\r
-                                    () -> scheduleLabelUpdate(),\r
-                                    LABEL_UPDATE_MIN_PERIOD_MS - timeSinceLastUpdate,\r
-                                    TimeUnit.MILLISECONDS);\r
-                        }\r
-                    }\r
-                    private void scheduleLabelUpdate() {\r
-                        lastUpdateTime = System.currentTimeMillis();\r
-                        timedUpdate = null;\r
-\r
-                        uisync.asyncExec(() -> {\r
-                            //System.out.println("updating time label: " + time);\r
-                            //System.out.println("label isdisposed: " + label.isDisposed());\r
-                            if (!label.isDisposed()) {\r
-                                updateLabel();\r
-                                if (lastState != currentState) {\r
-                                    setLabelVisualsByState(currentState);\r
-                                    lastState = currentState;\r
-                                }\r
-                            }\r
-                        });\r
-                    }\r
-                    @Override\r
-                    public void stateChanged(ExperimentState state) {\r
-                        //System.out.println("TimerContribution: state changed: " + state);\r
-                        currentState = state;\r
-                        if (state == ExperimentState.DISPOSED)\r
-                            experiment.removeListener(_this);\r
-                        else\r
-                            scheduleLabelUpdate();\r
-                    }\r
-                };\r
-                experiment.addListener(listener);\r
-\r
-                currentExperiment = dynExp;\r
-                currentListener = listener;\r
-            }\r
-        };\r
-\r
-        experimentManager = manager;\r
-        manager.addListener(experimentManagerListener);\r
-    }\r
-\r
-    private void toggleMode() {\r
-        mode = mode.next();\r
-        if (!label.isDisposed()) {\r
-            updateLabel();\r
-            updateTooltip();\r
-            setTimeFormatPreference(mode);\r
-        }\r
-    }\r
-\r
-    private void setTimeFormatPreference(Mode mode) {\r
-        if (chartPreferences != null) {\r
-            chartPreferences.put(PREF_CHART_TIMEFORMAT, toTimeFormatPreference(mode));\r
-        }\r
-    }\r
-\r
-    private void updateTooltip() {\r
-        if (label.isDisposed())\r
-            return;\r
-        switch (mode) {\r
-            case HMS:\r
-                label.setToolTipText("Shows simulation time in HMS");\r
-                break;\r
-            case SECONDS:\r
-                label.setToolTipText("Shows simulation time in seconds");\r
-                break;\r
-        }\r
-    }\r
-\r
-    private void updateLabel() {\r
-        // Try to keep selection.\r
-        Point selection = label.getSelection();\r
-        String oldText = label.getText();\r
-        String newText = getTime();\r
-        if (selection.y == oldText.length())\r
-            selection.y = newText.length();\r
-        else\r
-            selection.y = Math.min(selection.y, newText.length());\r
-\r
-        label.setText(newText);\r
-        label.setSelection(selection);\r
-        Point newSize = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);\r
-        if (!ObjectUtils.objectEquals(newSize, size)) {\r
-            label.setSize(newSize);\r
-            size = newSize;\r
-            Composite parent = label.getParent();\r
-            if (parent != null) {\r
-                parent.layout();\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * @param currentState\r
-     * @thread SWT\r
-     */\r
-    private void setLabelVisualsByState(ExperimentState currentState) {\r
-        if (label.isDisposed())\r
-            return;\r
-        switch (currentState) {\r
-            case RUNNING:\r
-                label.setBackground((Color) resourceManager.get(RUNNING_BG));\r
-                label.setForeground((Color) resourceManager.get(RUNNING_FG));\r
-                //label.setFont((Font) resourceManager.get(FontDescriptor.createFrom(label.getFont()).setStyle(SWT.BOLD)));\r
-                label.setEnabled(true);\r
-                break;\r
-            case STOPPED:\r
-                label.setBackground(null);\r
-                label.setForeground(null);\r
-                label.setFont(null);\r
-                label.setEnabled(true);\r
-                break;\r
-            case INITIALIZING:\r
-            case DISPOSED:\r
-                label.setBackground(null);\r
-                label.setForeground(null);\r
-                label.setFont(null);\r
-                label.setEnabled(false);\r
-                break;\r
-        }\r
-    }\r
-\r
-    private String getTime() {\r
-        if (mode == Mode.SECONDS)\r
-            return SimulationTimeUtil.formatSeconds(time);\r
-        return SimulationTimeUtil.formatHMSS(time);\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Semantum Oy - initial API and implementation
+ *******************************************************************************/
+package org.simantics.simulation.ui.handlers.e4;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
+import org.eclipse.e4.core.di.annotations.Optional;
+import org.eclipse.e4.core.di.extensions.Preference;
+import org.eclipse.e4.ui.di.UISynchronize;
+import org.eclipse.e4.ui.model.application.ui.menu.MToolControl;
+import org.eclipse.jface.resource.ColorDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+import org.simantics.databoard.util.ObjectUtils;
+import org.simantics.project.IProject;
+import org.simantics.simulation.experiment.ExperimentState;
+import org.simantics.simulation.experiment.IDynamicExperiment;
+import org.simantics.simulation.experiment.IDynamicExperimentListener;
+import org.simantics.simulation.experiment.IExperiment;
+import org.simantics.simulation.experiment.IExperimentListener;
+import org.simantics.simulation.experiment.SimulationTimeUtil;
+import org.simantics.simulation.project.IExperimentManager;
+import org.simantics.simulation.project.IExperimentManagerListener;
+import org.simantics.ui.SimanticsUI;
+import org.simantics.utils.threads.ThreadUtils;
+
+/**
+ * E4-version of the old
+ * {@link org.simantics.simulation.ui.handlers.TimerContribution}.
+ * 
+ * <p>
+ * Bound to org.simantics.chart/chart.timeformat preference for the used time
+ * format. This is not the nicest of solutions since it makes the
+ * <code>org.simantics.simulation.ui</code> plug-in depend on the
+ * <code>org.simantics.charts</code> plug-in. However the binding is optional
+ * and therefore the thin dependency is acceptable for now.
+ * 
+ * @author Jani Simomaa / Semantum Oy
+ * @author Tuukka Lehtonen / Semantum Oy
+ * @since 1.22
+ */
+public class TimerContribution {
+
+    private static final String PREF_CHART_BUNDLE_ID = "org.simantics.charts";
+    private static final String PREF_CHART_TIMEFORMAT = "chart.timeformat";
+
+    private static final long LABEL_UPDATE_MIN_PERIOD_MS = 100;
+
+    enum Mode {
+        HMS,
+        SECONDS;
+        Mode next() {
+            switch (this) {
+                case HMS: return SECONDS;
+                case SECONDS: return HMS;
+                default: return HMS;
+            }
+        }
+    }
+
+    boolean              disposed = false;
+    Text                 label;
+    Point                size;
+    double               time     = 0.0;
+    private Mode         mode     = Mode.HMS;
+
+    private IExperimentManager experimentManager;
+    private IExperimentManagerListener experimentManagerListener;
+    private ExperimentState currentState;
+
+    private ResourceManager resourceManager;
+
+    @Inject
+    private UISynchronize uisync;
+
+    /**
+     * For listening to the current chart time format preference.
+     */
+    @Inject
+    @Optional
+    @Preference(nodePath = PREF_CHART_BUNDLE_ID)
+    private IEclipsePreferences chartPreferences;
+
+    private static String toTimeFormatPreference(Mode mode) {
+        switch (mode) {
+        case SECONDS: return "Decimal";
+        case HMS:
+        default: return "Time";
+        }
+    }
+
+    private static Mode toMode(String timeFormat) {
+        if (timeFormat == null)
+            return Mode.HMS;
+        switch (timeFormat) {
+        case "Decimal": return Mode.SECONDS;
+        case "Time":
+        default: return Mode.HMS;
+        }
+    }
+
+    private IPreferenceChangeListener chartTimeFormatListener = event -> {
+        if (PREF_CHART_TIMEFORMAT.equals(event.getKey())) {
+            Mode newMode = toMode((String) event.getNewValue());
+            if (newMode != mode) {
+                mode = newMode;
+                uisync.asyncExec(() -> {
+                    if (!label.isDisposed()) {
+                        updateLabel();
+                        updateTooltip();
+                    }
+                });
+            }
+        }
+    };
+
+    private static ColorDescriptor     RUNNING_BG = ColorDescriptor.createFrom(new RGB(0, 128, 0));
+    private static ColorDescriptor     RUNNING_FG = ColorDescriptor.createFrom(new RGB(255, 255, 255));
+
+    @PostConstruct
+    public void createControls(Composite parent, MToolControl toolControl) {
+        IProject project = SimanticsUI.peekProject();
+        if (project == null)
+            return;
+
+        IExperimentManager manager = project.getHint(IExperimentManager.KEY_EXPERIMENT_MANAGER);
+        if(manager == null)
+            return;
+
+        label = new Text(parent, SWT.BORDER | SWT.CENTER | SWT.READ_ONLY);
+        label.setEnabled(false);
+        label.setText(getTime());
+        label.setToolTipText("Simulation Timer");
+
+        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), label);
+
+        updateTooltip();
+
+        Listener labelListener = new Listener() {
+            boolean pressed = false;
+            boolean inside = false;
+            @Override
+            public void handleEvent(Event event) {
+                switch (event.type) {
+                    case SWT.MouseDown:
+                        if (inside && (event.button == 1 || event.button == 2))
+                            pressed = true;
+                        break;
+                    case SWT.MouseUp:
+                        if (pressed && inside) {
+                            pressed = false;
+                            toggleMode();
+                        }
+                        break;
+                    case SWT.MouseEnter:
+                        inside = true;
+                        break;
+                    case SWT.MouseExit:
+                        inside = false;
+                        break;
+                }
+            }
+        };
+        label.addListener(SWT.MouseDown, labelListener);
+        label.addListener(SWT.MouseEnter, labelListener);
+        label.addListener(SWT.MouseExit, labelListener);
+        label.addListener(SWT.MouseUp, labelListener);
+
+        size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
+        if (currentState != null)
+            setLabelVisualsByState(currentState);
+
+        attachToExperimentManager(manager);
+
+        if (chartPreferences != null) {
+            chartPreferences.addPreferenceChangeListener(chartTimeFormatListener);
+            mode = toMode((String) chartPreferences.get(PREF_CHART_TIMEFORMAT, null));
+        }
+    }
+
+    @PreDestroy
+    public void destroy() {
+        if (chartPreferences != null) {
+            chartPreferences.removePreferenceChangeListener(chartTimeFormatListener);
+        }
+    }
+
+    private void attachToExperimentManager(final IExperimentManager manager) {
+        if (experimentManager != null) {
+            if (experimentManager.equals(manager))
+                return;
+            experimentManager.removeListener(experimentManagerListener);
+        }
+        if (manager == null)
+            return;
+
+        //System.out.println(this + "(" + System.identityHashCode(this) + ") ATTACH TO EXPERIMENT MANAGER " + manager);
+
+        experimentManagerListener = new IExperimentManagerListener() {
+            IDynamicExperiment currentExperiment;
+            IExperimentListener currentListener;
+
+            @Override
+            public void managerDisposed() {
+                manager.removeListener(this);
+            }
+            @Override
+            public void activeExperimentUnloaded() {
+                attachToExperiment(null);
+            }
+            @Override
+            public void activeExperimentLoaded(IExperiment experiment) {
+                attachToExperiment(experiment);
+            }
+            synchronized void attachToExperiment(final IExperiment experiment) {
+                if (currentExperiment != null) {
+                    currentExperiment.removeListener(currentListener);
+                    currentExperiment = null;
+                    currentListener = null;
+                }
+
+                if (!(experiment instanceof IDynamicExperiment)) {
+                    // Ensure that the timer text value is reset to zero.
+                    time = 0;
+                    uisync.asyncExec(() -> {
+                        if (!label.isDisposed()) {
+                            updateLabel();
+                            setLabelVisualsByState(ExperimentState.DISPOSED);
+                        }
+                    });
+                    return;
+                }
+
+                IDynamicExperiment dynExp = (IDynamicExperiment) experiment;
+                //System.out.println(TimerContribution.this + "(" + System.identityHashCode(TimerContribution.this) + ") ATTACH TO EXPERIMENT " + dynExp);
+
+                IDynamicExperimentListener listener = new IDynamicExperimentListener() {
+                    final IExperimentListener _this = this;
+                    long lastUpdateTime = 0;
+                    ScheduledFuture<?> timedUpdate = null;
+                    ExperimentState lastState = null;
+                    @Override
+                    public void timeChanged(double newTime) {
+                        //System.out.println(this + ".timeChanged: " + newTime);
+                        time = newTime;
+
+                        ScheduledFuture<?> f = timedUpdate;
+                        if (f != null && !f.isDone())
+                            return;
+
+                        long timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateTime;
+
+                        if (timeSinceLastUpdate > LABEL_UPDATE_MIN_PERIOD_MS) {
+                            scheduleLabelUpdate();
+                        } else {
+                            timedUpdate = ThreadUtils.getNonBlockingWorkExecutor().schedule(
+                                    () -> scheduleLabelUpdate(),
+                                    LABEL_UPDATE_MIN_PERIOD_MS - timeSinceLastUpdate,
+                                    TimeUnit.MILLISECONDS);
+                        }
+                    }
+                    private void scheduleLabelUpdate() {
+                        lastUpdateTime = System.currentTimeMillis();
+                        timedUpdate = null;
+
+                        uisync.asyncExec(() -> {
+                            //System.out.println("updating time label: " + time);
+                            //System.out.println("label isdisposed: " + label.isDisposed());
+                            if (!label.isDisposed()) {
+                                updateLabel();
+                                if (lastState != currentState) {
+                                    setLabelVisualsByState(currentState);
+                                    lastState = currentState;
+                                }
+                            }
+                        });
+                    }
+                    @Override
+                    public void stateChanged(ExperimentState state) {
+                        //System.out.println("TimerContribution: state changed: " + state);
+                        currentState = state;
+                        if (state == ExperimentState.DISPOSED)
+                            experiment.removeListener(_this);
+                        else
+                            scheduleLabelUpdate();
+                    }
+                };
+                experiment.addListener(listener);
+
+                currentExperiment = dynExp;
+                currentListener = listener;
+            }
+        };
+
+        experimentManager = manager;
+        manager.addListener(experimentManagerListener);
+    }
+
+    private void toggleMode() {
+        mode = mode.next();
+        if (!label.isDisposed()) {
+            updateLabel();
+            updateTooltip();
+            setTimeFormatPreference(mode);
+        }
+    }
+
+    private void setTimeFormatPreference(Mode mode) {
+        if (chartPreferences != null) {
+            chartPreferences.put(PREF_CHART_TIMEFORMAT, toTimeFormatPreference(mode));
+        }
+    }
+
+    private void updateTooltip() {
+        if (label.isDisposed())
+            return;
+        switch (mode) {
+            case HMS:
+                label.setToolTipText("Shows simulation time in HMS");
+                break;
+            case SECONDS:
+                label.setToolTipText("Shows simulation time in seconds");
+                break;
+        }
+    }
+
+    private void updateLabel() {
+        // Try to keep selection.
+        Point selection = label.getSelection();
+        String oldText = label.getText();
+        String newText = getTime();
+        if (selection.y == oldText.length())
+            selection.y = newText.length();
+        else
+            selection.y = Math.min(selection.y, newText.length());
+
+        label.setText(newText);
+        label.setSelection(selection);
+        Point newSize = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
+        if (!ObjectUtils.objectEquals(newSize, size)) {
+            label.setSize(newSize);
+            size = newSize;
+            Composite parent = label.getParent();
+            if (parent != null) {
+                parent.layout();
+            }
+        }
+    }
+
+    /**
+     * @param currentState
+     * @thread SWT
+     */
+    private void setLabelVisualsByState(ExperimentState currentState) {
+        if (label.isDisposed())
+            return;
+        switch (currentState) {
+            case RUNNING:
+                label.setBackground((Color) resourceManager.get(RUNNING_BG));
+                label.setForeground((Color) resourceManager.get(RUNNING_FG));
+                //label.setFont((Font) resourceManager.get(FontDescriptor.createFrom(label.getFont()).setStyle(SWT.BOLD)));
+                label.setEnabled(true);
+                break;
+            case STOPPED:
+                label.setBackground(null);
+                label.setForeground(null);
+                label.setFont(null);
+                label.setEnabled(true);
+                break;
+            case INITIALIZING:
+            case DISPOSED:
+                label.setBackground(null);
+                label.setForeground(null);
+                label.setFont(null);
+                label.setEnabled(false);
+                break;
+        }
+    }
+
+    private String getTime() {
+        if (mode == Mode.SECONDS)
+            return SimulationTimeUtil.formatSeconds(time);
+        return SimulationTimeUtil.formatHMSS(time);
+    }
+
+}