/******************************************************************************* * 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.Simantics; 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.utils.threads.ThreadUtils; /** * E4-version of the old * {@link org.simantics.simulation.ui.handlers.TimerContribution}. * *

* Bound to org.simantics.chart/chart.timeformat preference for the used time * format. This is not the nicest of solutions since it makes the * org.simantics.simulation.ui plug-in depend on the * org.simantics.charts 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 = Simantics.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); } }