]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.simulation.ui/src/org/simantics/simulation/ui/handlers/e4/TimerContribution.java
a5f3fa951198c2720dc7ed53450504d3a2a991cc
[simantics/platform.git] / bundles / org.simantics.simulation.ui / src / org / simantics / simulation / ui / handlers / e4 / TimerContribution.java
1 /*******************************************************************************
2  * Copyright (c) 2015, 2016 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     Semantum Oy - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.simulation.ui.handlers.e4;
13
14 import java.util.concurrent.ScheduledFuture;
15 import java.util.concurrent.TimeUnit;
16
17 import javax.annotation.PostConstruct;
18 import javax.annotation.PreDestroy;
19 import javax.inject.Inject;
20
21 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
22 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
23 import org.eclipse.e4.core.di.annotations.Optional;
24 import org.eclipse.e4.core.di.extensions.Preference;
25 import org.eclipse.e4.ui.di.UISynchronize;
26 import org.eclipse.e4.ui.model.application.ui.menu.MToolControl;
27 import org.eclipse.jface.resource.ColorDescriptor;
28 import org.eclipse.jface.resource.JFaceResources;
29 import org.eclipse.jface.resource.LocalResourceManager;
30 import org.eclipse.jface.resource.ResourceManager;
31 import org.eclipse.swt.SWT;
32 import org.eclipse.swt.graphics.Color;
33 import org.eclipse.swt.graphics.Point;
34 import org.eclipse.swt.graphics.RGB;
35 import org.eclipse.swt.widgets.Composite;
36 import org.eclipse.swt.widgets.Event;
37 import org.eclipse.swt.widgets.Listener;
38 import org.eclipse.swt.widgets.Text;
39 import org.simantics.databoard.util.ObjectUtils;
40 import org.simantics.project.IProject;
41 import org.simantics.simulation.experiment.ExperimentState;
42 import org.simantics.simulation.experiment.IDynamicExperiment;
43 import org.simantics.simulation.experiment.IDynamicExperimentListener;
44 import org.simantics.simulation.experiment.IExperiment;
45 import org.simantics.simulation.experiment.IExperimentListener;
46 import org.simantics.simulation.experiment.SimulationTimeUtil;
47 import org.simantics.simulation.project.IExperimentManager;
48 import org.simantics.simulation.project.IExperimentManagerListener;
49 import org.simantics.ui.SimanticsUI;
50 import org.simantics.utils.threads.ThreadUtils;
51
52 /**
53  * E4-version of the old
54  * {@link org.simantics.simulation.ui.handlers.TimerContribution}.
55  * 
56  * <p>
57  * Bound to org.simantics.chart/chart.timeformat preference for the used time
58  * format. This is not the nicest of solutions since it makes the
59  * <code>org.simantics.simulation.ui</code> plug-in depend on the
60  * <code>org.simantics.charts</code> plug-in. However the binding is optional
61  * and therefore the thin dependency is acceptable for now.
62  * 
63  * @author Jani Simomaa / Semantum Oy
64  * @author Tuukka Lehtonen / Semantum Oy
65  * @since 1.22
66  */
67 public class TimerContribution {
68
69     private static final String PREF_CHART_BUNDLE_ID = "org.simantics.charts";
70     private static final String PREF_CHART_TIMEFORMAT = "chart.timeformat";
71
72     private static final long LABEL_UPDATE_MIN_PERIOD_MS = 100;
73
74     enum Mode {
75         HMS,
76         SECONDS;
77         Mode next() {
78             switch (this) {
79                 case HMS: return SECONDS;
80                 case SECONDS: return HMS;
81                 default: return HMS;
82             }
83         }
84     }
85
86     boolean              disposed = false;
87     Text                 label;
88     Point                size;
89     double               time     = 0.0;
90     private Mode         mode     = Mode.HMS;
91
92     private IExperimentManager experimentManager;
93     private IExperimentManagerListener experimentManagerListener;
94     private ExperimentState currentState;
95
96     private ResourceManager resourceManager;
97
98     @Inject
99     private UISynchronize uisync;
100
101     /**
102      * For listening to the current chart time format preference.
103      */
104     @Inject
105     @Optional
106     @Preference(nodePath = PREF_CHART_BUNDLE_ID)
107     private IEclipsePreferences chartPreferences;
108
109     private static String toTimeFormatPreference(Mode mode) {
110         switch (mode) {
111         case SECONDS: return "Decimal";
112         case HMS:
113         default: return "Time";
114         }
115     }
116
117     private static Mode toMode(String timeFormat) {
118         if (timeFormat == null)
119             return Mode.HMS;
120         switch (timeFormat) {
121         case "Decimal": return Mode.SECONDS;
122         case "Time":
123         default: return Mode.HMS;
124         }
125     }
126
127     private IPreferenceChangeListener chartTimeFormatListener = event -> {
128         if (PREF_CHART_TIMEFORMAT.equals(event.getKey())) {
129             Mode newMode = toMode((String) event.getNewValue());
130             if (newMode != mode) {
131                 mode = newMode;
132                 uisync.asyncExec(() -> {
133                     if (!label.isDisposed()) {
134                         updateLabel();
135                         updateTooltip();
136                     }
137                 });
138             }
139         }
140     };
141
142     private static ColorDescriptor     RUNNING_BG = ColorDescriptor.createFrom(new RGB(0, 128, 0));
143     private static ColorDescriptor     RUNNING_FG = ColorDescriptor.createFrom(new RGB(255, 255, 255));
144
145     @PostConstruct
146     public void createControls(Composite parent, MToolControl toolControl) {
147         IProject project = SimanticsUI.peekProject();
148         if (project == null)
149             return;
150
151         IExperimentManager manager = project.getHint(IExperimentManager.KEY_EXPERIMENT_MANAGER);
152         if(manager == null)
153             return;
154
155         label = new Text(parent, SWT.BORDER | SWT.CENTER | SWT.READ_ONLY);
156         label.setEnabled(false);
157         label.setText(getTime());
158         label.setToolTipText("Simulation Timer");
159
160         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), label);
161
162         updateTooltip();
163
164         Listener labelListener = new Listener() {
165             boolean pressed = false;
166             boolean inside = false;
167             @Override
168             public void handleEvent(Event event) {
169                 switch (event.type) {
170                     case SWT.MouseDown:
171                         if (inside && (event.button == 1 || event.button == 2))
172                             pressed = true;
173                         break;
174                     case SWT.MouseUp:
175                         if (pressed && inside) {
176                             pressed = false;
177                             toggleMode();
178                         }
179                         break;
180                     case SWT.MouseEnter:
181                         inside = true;
182                         break;
183                     case SWT.MouseExit:
184                         inside = false;
185                         break;
186                 }
187             }
188         };
189         label.addListener(SWT.MouseDown, labelListener);
190         label.addListener(SWT.MouseEnter, labelListener);
191         label.addListener(SWT.MouseExit, labelListener);
192         label.addListener(SWT.MouseUp, labelListener);
193
194         size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
195         if (currentState != null)
196             setLabelVisualsByState(currentState);
197
198         attachToExperimentManager(manager);
199
200         if (chartPreferences != null) {
201             chartPreferences.addPreferenceChangeListener(chartTimeFormatListener);
202             mode = toMode((String) chartPreferences.get(PREF_CHART_TIMEFORMAT, null));
203         }
204     }
205
206     @PreDestroy
207     public void destroy() {
208         if (chartPreferences != null) {
209             chartPreferences.removePreferenceChangeListener(chartTimeFormatListener);
210         }
211     }
212
213     private void attachToExperimentManager(final IExperimentManager manager) {
214         if (experimentManager != null) {
215             if (experimentManager.equals(manager))
216                 return;
217             experimentManager.removeListener(experimentManagerListener);
218         }
219         if (manager == null)
220             return;
221
222         //System.out.println(this + "(" + System.identityHashCode(this) + ") ATTACH TO EXPERIMENT MANAGER " + manager);
223
224         experimentManagerListener = new IExperimentManagerListener() {
225             IDynamicExperiment currentExperiment;
226             IExperimentListener currentListener;
227
228             @Override
229             public void managerDisposed() {
230                 manager.removeListener(this);
231             }
232             @Override
233             public void activeExperimentUnloaded() {
234                 attachToExperiment(null);
235             }
236             @Override
237             public void activeExperimentLoaded(IExperiment experiment) {
238                 attachToExperiment(experiment);
239             }
240             synchronized void attachToExperiment(final IExperiment experiment) {
241                 if (currentExperiment != null) {
242                     currentExperiment.removeListener(currentListener);
243                     currentExperiment = null;
244                     currentListener = null;
245                 }
246
247                 if (!(experiment instanceof IDynamicExperiment)) {
248                     // Ensure that the timer text value is reset to zero.
249                     time = 0;
250                     uisync.asyncExec(() -> {
251                         if (!label.isDisposed()) {
252                             updateLabel();
253                             setLabelVisualsByState(ExperimentState.DISPOSED);
254                         }
255                     });
256                     return;
257                 }
258
259                 IDynamicExperiment dynExp = (IDynamicExperiment) experiment;
260                 //System.out.println(TimerContribution.this + "(" + System.identityHashCode(TimerContribution.this) + ") ATTACH TO EXPERIMENT " + dynExp);
261
262                 IDynamicExperimentListener listener = new IDynamicExperimentListener() {
263                     final IExperimentListener _this = this;
264                     long lastUpdateTime = 0;
265                     ScheduledFuture<?> timedUpdate = null;
266                     ExperimentState lastState = null;
267                     @Override
268                     public void timeChanged(double newTime) {
269                         //System.out.println(this + ".timeChanged: " + newTime);
270                         time = newTime;
271
272                         ScheduledFuture<?> f = timedUpdate;
273                         if (f != null && !f.isDone())
274                             return;
275
276                         long timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateTime;
277
278                         if (timeSinceLastUpdate > LABEL_UPDATE_MIN_PERIOD_MS) {
279                             scheduleLabelUpdate();
280                         } else {
281                             timedUpdate = ThreadUtils.getNonBlockingWorkExecutor().schedule(
282                                     () -> scheduleLabelUpdate(),
283                                     LABEL_UPDATE_MIN_PERIOD_MS - timeSinceLastUpdate,
284                                     TimeUnit.MILLISECONDS);
285                         }
286                     }
287                     private void scheduleLabelUpdate() {
288                         lastUpdateTime = System.currentTimeMillis();
289                         timedUpdate = null;
290
291                         uisync.asyncExec(() -> {
292                             //System.out.println("updating time label: " + time);
293                             //System.out.println("label isdisposed: " + label.isDisposed());
294                             if (!label.isDisposed()) {
295                                 updateLabel();
296                                 if (lastState != currentState) {
297                                     setLabelVisualsByState(currentState);
298                                     lastState = currentState;
299                                 }
300                             }
301                         });
302                     }
303                     @Override
304                     public void stateChanged(ExperimentState state) {
305                         //System.out.println("TimerContribution: state changed: " + state);
306                         currentState = state;
307                         if (state == ExperimentState.DISPOSED)
308                             experiment.removeListener(_this);
309                         else
310                             scheduleLabelUpdate();
311                     }
312                 };
313                 experiment.addListener(listener);
314
315                 currentExperiment = dynExp;
316                 currentListener = listener;
317             }
318         };
319
320         experimentManager = manager;
321         manager.addListener(experimentManagerListener);
322     }
323
324     private void toggleMode() {
325         mode = mode.next();
326         if (!label.isDisposed()) {
327             updateLabel();
328             updateTooltip();
329             setTimeFormatPreference(mode);
330         }
331     }
332
333     private void setTimeFormatPreference(Mode mode) {
334         if (chartPreferences != null) {
335             chartPreferences.put(PREF_CHART_TIMEFORMAT, toTimeFormatPreference(mode));
336         }
337     }
338
339     private void updateTooltip() {
340         if (label.isDisposed())
341             return;
342         switch (mode) {
343             case HMS:
344                 label.setToolTipText("Shows simulation time in HMS");
345                 break;
346             case SECONDS:
347                 label.setToolTipText("Shows simulation time in seconds");
348                 break;
349         }
350     }
351
352     private void updateLabel() {
353         // Try to keep selection.
354         Point selection = label.getSelection();
355         String oldText = label.getText();
356         String newText = getTime();
357         if (selection.y == oldText.length())
358             selection.y = newText.length();
359         else
360             selection.y = Math.min(selection.y, newText.length());
361
362         label.setText(newText);
363         label.setSelection(selection);
364         Point newSize = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
365         if (!ObjectUtils.objectEquals(newSize, size)) {
366             label.setSize(newSize);
367             size = newSize;
368             Composite parent = label.getParent();
369             if (parent != null) {
370                 parent.layout();
371             }
372         }
373     }
374
375     /**
376      * @param currentState
377      * @thread SWT
378      */
379     private void setLabelVisualsByState(ExperimentState currentState) {
380         if (label.isDisposed())
381             return;
382         switch (currentState) {
383             case RUNNING:
384                 label.setBackground((Color) resourceManager.get(RUNNING_BG));
385                 label.setForeground((Color) resourceManager.get(RUNNING_FG));
386                 //label.setFont((Font) resourceManager.get(FontDescriptor.createFrom(label.getFont()).setStyle(SWT.BOLD)));
387                 label.setEnabled(true);
388                 break;
389             case STOPPED:
390                 label.setBackground(null);
391                 label.setForeground(null);
392                 label.setFont(null);
393                 label.setEnabled(true);
394                 break;
395             case INITIALIZING:
396             case DISPOSED:
397                 label.setBackground(null);
398                 label.setForeground(null);
399                 label.setFont(null);
400                 label.setEnabled(false);
401                 break;
402         }
403     }
404
405     private String getTime() {
406         if (mode == Mode.SECONDS)
407             return SimulationTimeUtil.formatSeconds(time);
408         return SimulationTimeUtil.formatHMSS(time);
409     }
410
411 }