]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.simulation.ui/src/org/simantics/simulation/ui/handlers/e4/TimerContribution.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.simulation.ui / src / org / simantics / simulation / ui / handlers / e4 / TimerContribution.java
diff --git a/bundles/org.simantics.simulation.ui/src/org/simantics/simulation/ui/handlers/e4/TimerContribution.java b/bundles/org.simantics.simulation.ui/src/org/simantics/simulation/ui/handlers/e4/TimerContribution.java
new file mode 100644 (file)
index 0000000..848be40
--- /dev/null
@@ -0,0 +1,411 @@
+/*******************************************************************************\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