--- /dev/null
+/*******************************************************************************\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