1 /*******************************************************************************
\r
2 * Copyright (c) 2015, 2016 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * Semantum Oy - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.simulation.ui.handlers.e4;
\r
14 import java.util.concurrent.ScheduledFuture;
\r
15 import java.util.concurrent.TimeUnit;
\r
17 import javax.annotation.PostConstruct;
\r
18 import javax.annotation.PreDestroy;
\r
19 import javax.inject.Inject;
\r
21 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
\r
22 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
\r
23 import org.eclipse.e4.core.di.annotations.Optional;
\r
24 import org.eclipse.e4.core.di.extensions.Preference;
\r
25 import org.eclipse.e4.ui.di.UISynchronize;
\r
26 import org.eclipse.e4.ui.model.application.ui.menu.MToolControl;
\r
27 import org.eclipse.jface.resource.ColorDescriptor;
\r
28 import org.eclipse.jface.resource.JFaceResources;
\r
29 import org.eclipse.jface.resource.LocalResourceManager;
\r
30 import org.eclipse.jface.resource.ResourceManager;
\r
31 import org.eclipse.swt.SWT;
\r
32 import org.eclipse.swt.graphics.Color;
\r
33 import org.eclipse.swt.graphics.Point;
\r
34 import org.eclipse.swt.graphics.RGB;
\r
35 import org.eclipse.swt.widgets.Composite;
\r
36 import org.eclipse.swt.widgets.Event;
\r
37 import org.eclipse.swt.widgets.Listener;
\r
38 import org.eclipse.swt.widgets.Text;
\r
39 import org.simantics.databoard.util.ObjectUtils;
\r
40 import org.simantics.project.IProject;
\r
41 import org.simantics.simulation.experiment.ExperimentState;
\r
42 import org.simantics.simulation.experiment.IDynamicExperiment;
\r
43 import org.simantics.simulation.experiment.IDynamicExperimentListener;
\r
44 import org.simantics.simulation.experiment.IExperiment;
\r
45 import org.simantics.simulation.experiment.IExperimentListener;
\r
46 import org.simantics.simulation.experiment.SimulationTimeUtil;
\r
47 import org.simantics.simulation.project.IExperimentManager;
\r
48 import org.simantics.simulation.project.IExperimentManagerListener;
\r
49 import org.simantics.ui.SimanticsUI;
\r
50 import org.simantics.utils.threads.ThreadUtils;
\r
53 * E4-version of the old
\r
54 * {@link org.simantics.simulation.ui.handlers.TimerContribution}.
\r
57 * Bound to org.simantics.chart/chart.timeformat preference for the used time
\r
58 * format. This is not the nicest of solutions since it makes the
\r
59 * <code>org.simantics.simulation.ui</code> plug-in depend on the
\r
60 * <code>org.simantics.charts</code> plug-in. However the binding is optional
\r
61 * and therefore the thin dependency is acceptable for now.
\r
63 * @author Jani Simomaa / Semantum Oy
\r
64 * @author Tuukka Lehtonen / Semantum Oy
\r
67 public class TimerContribution {
\r
69 private static final String PREF_CHART_BUNDLE_ID = "org.simantics.charts";
\r
70 private static final String PREF_CHART_TIMEFORMAT = "chart.timeformat";
\r
72 private static final long LABEL_UPDATE_MIN_PERIOD_MS = 100;
\r
79 case HMS: return SECONDS;
\r
80 case SECONDS: return HMS;
\r
81 default: return HMS;
\r
86 boolean disposed = false;
\r
90 private Mode mode = Mode.HMS;
\r
92 private IExperimentManager experimentManager;
\r
93 private IExperimentManagerListener experimentManagerListener;
\r
94 private ExperimentState currentState;
\r
96 private ResourceManager resourceManager;
\r
99 private UISynchronize uisync;
\r
102 * For listening to the current chart time format preference.
\r
106 @Preference(nodePath = PREF_CHART_BUNDLE_ID)
\r
107 private IEclipsePreferences chartPreferences;
\r
109 private static String toTimeFormatPreference(Mode mode) {
\r
111 case SECONDS: return "Decimal";
\r
113 default: return "Time";
\r
117 private static Mode toMode(String timeFormat) {
\r
118 if (timeFormat == null)
\r
120 switch (timeFormat) {
\r
121 case "Decimal": return Mode.SECONDS;
\r
123 default: return Mode.HMS;
\r
127 private IPreferenceChangeListener chartTimeFormatListener = event -> {
\r
128 if (PREF_CHART_TIMEFORMAT.equals(event.getKey())) {
\r
129 Mode newMode = toMode((String) event.getNewValue());
\r
130 if (newMode != mode) {
\r
132 uisync.asyncExec(() -> {
\r
133 if (!label.isDisposed()) {
\r
142 private static ColorDescriptor RUNNING_BG = ColorDescriptor.createFrom(new RGB(0, 128, 0));
\r
143 private static ColorDescriptor RUNNING_FG = ColorDescriptor.createFrom(new RGB(255, 255, 255));
\r
146 public void createControls(Composite parent, MToolControl toolControl) {
\r
147 IProject project = SimanticsUI.peekProject();
\r
148 if (project == null)
\r
151 IExperimentManager manager = project.getHint(IExperimentManager.KEY_EXPERIMENT_MANAGER);
\r
152 if(manager == null)
\r
155 label = new Text(parent, SWT.BORDER | SWT.CENTER | SWT.READ_ONLY);
\r
156 label.setEnabled(false);
\r
157 label.setText(getTime());
\r
158 label.setToolTipText("Simulation Timer");
\r
160 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), label);
\r
164 Listener labelListener = new Listener() {
\r
165 boolean pressed = false;
\r
166 boolean inside = false;
\r
168 public void handleEvent(Event event) {
\r
169 switch (event.type) {
\r
170 case SWT.MouseDown:
\r
171 if (inside && (event.button == 1 || event.button == 2))
\r
175 if (pressed && inside) {
\r
180 case SWT.MouseEnter:
\r
183 case SWT.MouseExit:
\r
189 label.addListener(SWT.MouseDown, labelListener);
\r
190 label.addListener(SWT.MouseEnter, labelListener);
\r
191 label.addListener(SWT.MouseExit, labelListener);
\r
192 label.addListener(SWT.MouseUp, labelListener);
\r
194 size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
\r
195 if (currentState != null)
\r
196 setLabelVisualsByState(currentState);
\r
198 attachToExperimentManager(manager);
\r
200 if (chartPreferences != null) {
\r
201 chartPreferences.addPreferenceChangeListener(chartTimeFormatListener);
\r
202 mode = toMode((String) chartPreferences.get(PREF_CHART_TIMEFORMAT, null));
\r
207 public void destroy() {
\r
208 if (chartPreferences != null) {
\r
209 chartPreferences.removePreferenceChangeListener(chartTimeFormatListener);
\r
213 private void attachToExperimentManager(final IExperimentManager manager) {
\r
214 if (experimentManager != null) {
\r
215 if (experimentManager.equals(manager))
\r
217 experimentManager.removeListener(experimentManagerListener);
\r
219 if (manager == null)
\r
222 //System.out.println(this + "(" + System.identityHashCode(this) + ") ATTACH TO EXPERIMENT MANAGER " + manager);
\r
224 experimentManagerListener = new IExperimentManagerListener() {
\r
225 IDynamicExperiment currentExperiment;
\r
226 IExperimentListener currentListener;
\r
229 public void managerDisposed() {
\r
230 manager.removeListener(this);
\r
233 public void activeExperimentUnloaded() {
\r
234 attachToExperiment(null);
\r
237 public void activeExperimentLoaded(IExperiment experiment) {
\r
238 attachToExperiment(experiment);
\r
240 synchronized void attachToExperiment(final IExperiment experiment) {
\r
241 if (currentExperiment != null) {
\r
242 currentExperiment.removeListener(currentListener);
\r
243 currentExperiment = null;
\r
244 currentListener = null;
\r
247 if (!(experiment instanceof IDynamicExperiment)) {
\r
248 // Ensure that the timer text value is reset to zero.
\r
250 uisync.asyncExec(() -> {
\r
251 if (!label.isDisposed()) {
\r
253 setLabelVisualsByState(ExperimentState.DISPOSED);
\r
259 IDynamicExperiment dynExp = (IDynamicExperiment) experiment;
\r
260 //System.out.println(TimerContribution.this + "(" + System.identityHashCode(TimerContribution.this) + ") ATTACH TO EXPERIMENT " + dynExp);
\r
262 IDynamicExperimentListener listener = new IDynamicExperimentListener() {
\r
263 final IExperimentListener _this = this;
\r
264 long lastUpdateTime = 0;
\r
265 ScheduledFuture<?> timedUpdate = null;
\r
266 ExperimentState lastState = null;
\r
268 public void timeChanged(double newTime) {
\r
269 //System.out.println(this + ".timeChanged: " + newTime);
\r
272 ScheduledFuture<?> f = timedUpdate;
\r
273 if (f != null && !f.isDone())
\r
276 long timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateTime;
\r
278 if (timeSinceLastUpdate > LABEL_UPDATE_MIN_PERIOD_MS) {
\r
279 scheduleLabelUpdate();
\r
281 timedUpdate = ThreadUtils.getNonBlockingWorkExecutor().schedule(
\r
282 () -> scheduleLabelUpdate(),
\r
283 LABEL_UPDATE_MIN_PERIOD_MS - timeSinceLastUpdate,
\r
284 TimeUnit.MILLISECONDS);
\r
287 private void scheduleLabelUpdate() {
\r
288 lastUpdateTime = System.currentTimeMillis();
\r
289 timedUpdate = null;
\r
291 uisync.asyncExec(() -> {
\r
292 //System.out.println("updating time label: " + time);
\r
293 //System.out.println("label isdisposed: " + label.isDisposed());
\r
294 if (!label.isDisposed()) {
\r
296 if (lastState != currentState) {
\r
297 setLabelVisualsByState(currentState);
\r
298 lastState = currentState;
\r
304 public void stateChanged(ExperimentState state) {
\r
305 //System.out.println("TimerContribution: state changed: " + state);
\r
306 currentState = state;
\r
307 if (state == ExperimentState.DISPOSED)
\r
308 experiment.removeListener(_this);
\r
310 scheduleLabelUpdate();
\r
313 experiment.addListener(listener);
\r
315 currentExperiment = dynExp;
\r
316 currentListener = listener;
\r
320 experimentManager = manager;
\r
321 manager.addListener(experimentManagerListener);
\r
324 private void toggleMode() {
\r
325 mode = mode.next();
\r
326 if (!label.isDisposed()) {
\r
329 setTimeFormatPreference(mode);
\r
333 private void setTimeFormatPreference(Mode mode) {
\r
334 if (chartPreferences != null) {
\r
335 chartPreferences.put(PREF_CHART_TIMEFORMAT, toTimeFormatPreference(mode));
\r
339 private void updateTooltip() {
\r
340 if (label.isDisposed())
\r
344 label.setToolTipText("Shows simulation time in HMS");
\r
347 label.setToolTipText("Shows simulation time in seconds");
\r
352 private void updateLabel() {
\r
353 // Try to keep selection.
\r
354 Point selection = label.getSelection();
\r
355 String oldText = label.getText();
\r
356 String newText = getTime();
\r
357 if (selection.y == oldText.length())
\r
358 selection.y = newText.length();
\r
360 selection.y = Math.min(selection.y, newText.length());
\r
362 label.setText(newText);
\r
363 label.setSelection(selection);
\r
364 Point newSize = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
\r
365 if (!ObjectUtils.objectEquals(newSize, size)) {
\r
366 label.setSize(newSize);
\r
368 Composite parent = label.getParent();
\r
369 if (parent != null) {
\r
376 * @param currentState
\r
379 private void setLabelVisualsByState(ExperimentState currentState) {
\r
380 if (label.isDisposed())
\r
382 switch (currentState) {
\r
384 label.setBackground((Color) resourceManager.get(RUNNING_BG));
\r
385 label.setForeground((Color) resourceManager.get(RUNNING_FG));
\r
386 //label.setFont((Font) resourceManager.get(FontDescriptor.createFrom(label.getFont()).setStyle(SWT.BOLD)));
\r
387 label.setEnabled(true);
\r
390 label.setBackground(null);
\r
391 label.setForeground(null);
\r
392 label.setFont(null);
\r
393 label.setEnabled(true);
\r
397 label.setBackground(null);
\r
398 label.setForeground(null);
\r
399 label.setFont(null);
\r
400 label.setEnabled(false);
\r
405 private String getTime() {
\r
406 if (mode == Mode.SECONDS)
\r
407 return SimulationTimeUtil.formatSeconds(time);
\r
408 return SimulationTimeUtil.formatHMSS(time);
\r