/******************************************************************************* * 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}. * *
* 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 = 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);
}
}