]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.simulation.ui/src/org/simantics/simulation/ui/handlers/e4/TimerContribution.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.simulation.ui / src / org / simantics / simulation / ui / handlers / e4 / TimerContribution.java
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
8  *\r
9  * Contributors:\r
10  *     Semantum Oy - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.simulation.ui.handlers.e4;\r
13 \r
14 import java.util.concurrent.ScheduledFuture;\r
15 import java.util.concurrent.TimeUnit;\r
16 \r
17 import javax.annotation.PostConstruct;\r
18 import javax.annotation.PreDestroy;\r
19 import javax.inject.Inject;\r
20 \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
51 \r
52 /**\r
53  * E4-version of the old\r
54  * {@link org.simantics.simulation.ui.handlers.TimerContribution}.\r
55  * \r
56  * <p>\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
62  * \r
63  * @author Jani Simomaa / Semantum Oy\r
64  * @author Tuukka Lehtonen / Semantum Oy\r
65  * @since 1.22\r
66  */\r
67 public class TimerContribution {\r
68 \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
71 \r
72     private static final long LABEL_UPDATE_MIN_PERIOD_MS = 100;\r
73 \r
74     enum Mode {\r
75         HMS,\r
76         SECONDS;\r
77         Mode next() {\r
78             switch (this) {\r
79                 case HMS: return SECONDS;\r
80                 case SECONDS: return HMS;\r
81                 default: return HMS;\r
82             }\r
83         }\r
84     }\r
85 \r
86     boolean              disposed = false;\r
87     Text                 label;\r
88     Point                size;\r
89     double               time     = 0.0;\r
90     private Mode         mode     = Mode.HMS;\r
91 \r
92     private IExperimentManager experimentManager;\r
93     private IExperimentManagerListener experimentManagerListener;\r
94     private ExperimentState currentState;\r
95 \r
96     private ResourceManager resourceManager;\r
97 \r
98     @Inject\r
99     private UISynchronize uisync;\r
100 \r
101     /**\r
102      * For listening to the current chart time format preference.\r
103      */\r
104     @Inject\r
105     @Optional\r
106     @Preference(nodePath = PREF_CHART_BUNDLE_ID)\r
107     private IEclipsePreferences chartPreferences;\r
108 \r
109     private static String toTimeFormatPreference(Mode mode) {\r
110         switch (mode) {\r
111         case SECONDS: return "Decimal";\r
112         case HMS:\r
113         default: return "Time";\r
114         }\r
115     }\r
116 \r
117     private static Mode toMode(String timeFormat) {\r
118         if (timeFormat == null)\r
119             return Mode.HMS;\r
120         switch (timeFormat) {\r
121         case "Decimal": return Mode.SECONDS;\r
122         case "Time":\r
123         default: return Mode.HMS;\r
124         }\r
125     }\r
126 \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
131                 mode = newMode;\r
132                 uisync.asyncExec(() -> {\r
133                     if (!label.isDisposed()) {\r
134                         updateLabel();\r
135                         updateTooltip();\r
136                     }\r
137                 });\r
138             }\r
139         }\r
140     };\r
141 \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
144 \r
145     @PostConstruct\r
146     public void createControls(Composite parent, MToolControl toolControl) {\r
147         IProject project = SimanticsUI.peekProject();\r
148         if (project == null)\r
149             return;\r
150 \r
151         IExperimentManager manager = project.getHint(IExperimentManager.KEY_EXPERIMENT_MANAGER);\r
152         if(manager == null)\r
153             return;\r
154 \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
159 \r
160         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), label);\r
161 \r
162         updateTooltip();\r
163 \r
164         Listener labelListener = new Listener() {\r
165             boolean pressed = false;\r
166             boolean inside = false;\r
167             @Override\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
172                             pressed = true;\r
173                         break;\r
174                     case SWT.MouseUp:\r
175                         if (pressed && inside) {\r
176                             pressed = false;\r
177                             toggleMode();\r
178                         }\r
179                         break;\r
180                     case SWT.MouseEnter:\r
181                         inside = true;\r
182                         break;\r
183                     case SWT.MouseExit:\r
184                         inside = false;\r
185                         break;\r
186                 }\r
187             }\r
188         };\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
193 \r
194         size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);\r
195         if (currentState != null)\r
196             setLabelVisualsByState(currentState);\r
197 \r
198         attachToExperimentManager(manager);\r
199 \r
200         if (chartPreferences != null) {\r
201             chartPreferences.addPreferenceChangeListener(chartTimeFormatListener);\r
202             mode = toMode((String) chartPreferences.get(PREF_CHART_TIMEFORMAT, null));\r
203         }\r
204     }\r
205 \r
206     @PreDestroy\r
207     public void destroy() {\r
208         if (chartPreferences != null) {\r
209             chartPreferences.removePreferenceChangeListener(chartTimeFormatListener);\r
210         }\r
211     }\r
212 \r
213     private void attachToExperimentManager(final IExperimentManager manager) {\r
214         if (experimentManager != null) {\r
215             if (experimentManager.equals(manager))\r
216                 return;\r
217             experimentManager.removeListener(experimentManagerListener);\r
218         }\r
219         if (manager == null)\r
220             return;\r
221 \r
222         //System.out.println(this + "(" + System.identityHashCode(this) + ") ATTACH TO EXPERIMENT MANAGER " + manager);\r
223 \r
224         experimentManagerListener = new IExperimentManagerListener() {\r
225             IDynamicExperiment currentExperiment;\r
226             IExperimentListener currentListener;\r
227 \r
228             @Override\r
229             public void managerDisposed() {\r
230                 manager.removeListener(this);\r
231             }\r
232             @Override\r
233             public void activeExperimentUnloaded() {\r
234                 attachToExperiment(null);\r
235             }\r
236             @Override\r
237             public void activeExperimentLoaded(IExperiment experiment) {\r
238                 attachToExperiment(experiment);\r
239             }\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
245                 }\r
246 \r
247                 if (!(experiment instanceof IDynamicExperiment)) {\r
248                     // Ensure that the timer text value is reset to zero.\r
249                     time = 0;\r
250                     uisync.asyncExec(() -> {\r
251                         if (!label.isDisposed()) {\r
252                             updateLabel();\r
253                             setLabelVisualsByState(ExperimentState.DISPOSED);\r
254                         }\r
255                     });\r
256                     return;\r
257                 }\r
258 \r
259                 IDynamicExperiment dynExp = (IDynamicExperiment) experiment;\r
260                 //System.out.println(TimerContribution.this + "(" + System.identityHashCode(TimerContribution.this) + ") ATTACH TO EXPERIMENT " + dynExp);\r
261 \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
267                     @Override\r
268                     public void timeChanged(double newTime) {\r
269                         //System.out.println(this + ".timeChanged: " + newTime);\r
270                         time = newTime;\r
271 \r
272                         ScheduledFuture<?> f = timedUpdate;\r
273                         if (f != null && !f.isDone())\r
274                             return;\r
275 \r
276                         long timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateTime;\r
277 \r
278                         if (timeSinceLastUpdate > LABEL_UPDATE_MIN_PERIOD_MS) {\r
279                             scheduleLabelUpdate();\r
280                         } else {\r
281                             timedUpdate = ThreadUtils.getNonBlockingWorkExecutor().schedule(\r
282                                     () -> scheduleLabelUpdate(),\r
283                                     LABEL_UPDATE_MIN_PERIOD_MS - timeSinceLastUpdate,\r
284                                     TimeUnit.MILLISECONDS);\r
285                         }\r
286                     }\r
287                     private void scheduleLabelUpdate() {\r
288                         lastUpdateTime = System.currentTimeMillis();\r
289                         timedUpdate = null;\r
290 \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
295                                 updateLabel();\r
296                                 if (lastState != currentState) {\r
297                                     setLabelVisualsByState(currentState);\r
298                                     lastState = currentState;\r
299                                 }\r
300                             }\r
301                         });\r
302                     }\r
303                     @Override\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
309                         else\r
310                             scheduleLabelUpdate();\r
311                     }\r
312                 };\r
313                 experiment.addListener(listener);\r
314 \r
315                 currentExperiment = dynExp;\r
316                 currentListener = listener;\r
317             }\r
318         };\r
319 \r
320         experimentManager = manager;\r
321         manager.addListener(experimentManagerListener);\r
322     }\r
323 \r
324     private void toggleMode() {\r
325         mode = mode.next();\r
326         if (!label.isDisposed()) {\r
327             updateLabel();\r
328             updateTooltip();\r
329             setTimeFormatPreference(mode);\r
330         }\r
331     }\r
332 \r
333     private void setTimeFormatPreference(Mode mode) {\r
334         if (chartPreferences != null) {\r
335             chartPreferences.put(PREF_CHART_TIMEFORMAT, toTimeFormatPreference(mode));\r
336         }\r
337     }\r
338 \r
339     private void updateTooltip() {\r
340         if (label.isDisposed())\r
341             return;\r
342         switch (mode) {\r
343             case HMS:\r
344                 label.setToolTipText("Shows simulation time in HMS");\r
345                 break;\r
346             case SECONDS:\r
347                 label.setToolTipText("Shows simulation time in seconds");\r
348                 break;\r
349         }\r
350     }\r
351 \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
359         else\r
360             selection.y = Math.min(selection.y, newText.length());\r
361 \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
367             size = newSize;\r
368             Composite parent = label.getParent();\r
369             if (parent != null) {\r
370                 parent.layout();\r
371             }\r
372         }\r
373     }\r
374 \r
375     /**\r
376      * @param currentState\r
377      * @thread SWT\r
378      */\r
379     private void setLabelVisualsByState(ExperimentState currentState) {\r
380         if (label.isDisposed())\r
381             return;\r
382         switch (currentState) {\r
383             case RUNNING:\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
388                 break;\r
389             case STOPPED:\r
390                 label.setBackground(null);\r
391                 label.setForeground(null);\r
392                 label.setFont(null);\r
393                 label.setEnabled(true);\r
394                 break;\r
395             case INITIALIZING:\r
396             case DISPOSED:\r
397                 label.setBackground(null);\r
398                 label.setForeground(null);\r
399                 label.setFont(null);\r
400                 label.setEnabled(false);\r
401                 break;\r
402         }\r
403     }\r
404 \r
405     private String getTime() {\r
406         if (mode == Mode.SECONDS)\r
407             return SimulationTimeUtil.formatSeconds(time);\r
408         return SimulationTimeUtil.formatHMSS(time);\r
409     }\r
410 \r
411 }\r