Simulation speed now can be edited via a text field
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / editors / SimulationViewEditor.java
1 package net.mograsim.plugin.editors;
2
3 import java.io.ByteArrayInputStream;
4 import java.util.Optional;
5
6 import org.eclipse.core.resources.IFile;
7 import org.eclipse.core.runtime.CoreException;
8 import org.eclipse.core.runtime.IProgressMonitor;
9 import org.eclipse.jface.util.SafeRunnable;
10 import org.eclipse.swt.SWT;
11 import org.eclipse.swt.events.MouseEvent;
12 import org.eclipse.swt.events.MouseTrackListener;
13 import org.eclipse.swt.layout.FillLayout;
14 import org.eclipse.swt.layout.GridData;
15 import org.eclipse.swt.layout.GridLayout;
16 import org.eclipse.swt.widgets.Button;
17 import org.eclipse.swt.widgets.Composite;
18 import org.eclipse.swt.widgets.Display;
19 import org.eclipse.swt.widgets.Label;
20 import org.eclipse.swt.widgets.Scale;
21 import org.eclipse.ui.IEditorInput;
22 import org.eclipse.ui.IEditorSite;
23 import org.eclipse.ui.IFileEditorInput;
24 import org.eclipse.ui.PartInitException;
25 import org.eclipse.ui.part.EditorPart;
26
27 import net.haspamelodica.swt.helper.input.DoubleInput;
28 import net.haspamelodica.swt.helper.zoomablecanvas.helper.ZoomableCanvasUserInput;
29 import net.mograsim.logic.core.LogicObserver;
30 import net.mograsim.logic.core.components.CoreClock;
31 import net.mograsim.logic.model.LogicExecuter;
32 import net.mograsim.logic.model.LogicUICanvas;
33 import net.mograsim.machine.Machine;
34 import net.mograsim.machine.Memory.MemoryCellModifiedListener;
35 import net.mograsim.machine.mi.AssignableMicroInstructionMemory;
36 import net.mograsim.plugin.nature.MachineContext;
37 import net.mograsim.plugin.nature.MachineContext.ActiveMachineListener;
38 import net.mograsim.plugin.nature.ProjectMachineContext;
39 import net.mograsim.plugin.tables.DisplaySettings;
40 import net.mograsim.plugin.tables.mi.ActiveInstructionPreviewContentProvider;
41 import net.mograsim.plugin.tables.mi.InstructionTable;
42 import net.mograsim.preferences.Preferences;
43
44 //TODO what if we open multiple editors?
45 //TODO actually save / load register and latch states
46 public class SimulationViewEditor extends EditorPart
47 {
48         private static final int SIM_SPEED_SCALE_STEPS = 50;
49         private static final double SIM_SPEED_SCALE_STEP_FACTOR = 1.32;
50         private static final double SIM_SPEED_SCALE_STEP_FACTOR_LOG = Math.log(SIM_SPEED_SCALE_STEP_FACTOR);
51
52         private MachineContext context;
53
54         private LogicExecuter exec;
55         private Machine machine;
56
57         private Composite parent;
58         private Button resetButton;
59         private Button sbseButton;
60         private Button pauseButton;
61         private Scale simSpeedScale;
62         private DoubleInput simSpeedInput;
63         private Composite canvasParent;
64         private LogicUICanvas canvas;
65         private InstructionTable instPreview;
66         private Label noMachineLabel;
67
68         private ActiveMachineListener activeMachineListener;
69         private MemoryCellModifiedListener memCellListener;
70         private LogicObserver clockObserver;
71
72         public SimulationViewEditor()
73         {
74                 activeMachineListener = m -> recreateContextDependentControls();
75                 memCellListener = a -> instPreview.refresh();
76                 clockObserver = o ->
77                 {
78                         if (((CoreClock) o).isOn())
79                         {
80                                 exec.pauseLiveExecution();
81                                 if (!pauseButton.isDisposed())
82                                         Display.getDefault().asyncExec(() ->
83                                         {
84                                                 if (!pauseButton.isDisposed())
85                                                         pauseButton.setSelection(false);
86                                                 setPauseText(pauseButton, false);
87                                         });
88                         }
89                 };
90         }
91
92         @Override
93         public void createPartControl(Composite parent)
94         {
95                 this.parent = parent;
96                 // initialize UI
97                 parent.setLayout(new GridLayout());
98
99                 noMachineLabel = new Label(parent, SWT.NONE);
100                 noMachineLabel.setText("No machine present...");// TODO internationalize?
101                 addSimulationControlWidgets(parent);
102                 canvasParent = new Composite(parent, SWT.NONE);
103                 canvasParent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
104                 canvasParent.setLayout(new FillLayout());
105                 addInstructionPreviewControlWidgets(parent);
106                 recreateContextDependentControls();
107         }
108
109         private void recreateContextDependentControls()
110         {
111                 if (parent == null)
112                         // createPartControls has not been called yet
113                         return;
114
115                 double offX;
116                 double offY;
117                 double zoom;
118                 stopExecAndDeregisterContextDependentListeners();
119                 if (canvas != null)
120                 {
121                         offX = canvas.getOffX();
122                         offY = canvas.getOffY();
123                         zoom = canvas.getZoom();
124                         canvas.dispose();
125                 } else
126                 {
127                         offX = 0;
128                         offY = 0;
129                         zoom = -1;
130                 }
131
132                 Optional<Machine> machineOptional;
133                 if (context != null && (machineOptional = context.getActiveMachine()).isPresent())
134                 {
135                         noMachineLabel.setVisible(false);
136                         resetButton.setEnabled(true);
137                         sbseButton.setEnabled(true);
138                         pauseButton.setEnabled(true);
139                         simSpeedScale.setEnabled(true);
140                         simSpeedInput.setEnabled(true);
141
142                         machine = machineOptional.get();
143                         canvas = new LogicUICanvas(canvasParent, SWT.NONE, machine.getModel());
144                         canvas.addListener(SWT.MouseDown, e -> canvas.setFocus());
145                         ZoomableCanvasUserInput userInput = new ZoomableCanvasUserInput(canvas);
146                         userInput.buttonDrag = Preferences.current().getInt("net.mograsim.logic.model.button.drag");
147                         userInput.buttonZoom = Preferences.current().getInt("net.mograsim.logic.model.button.zoom");
148                         userInput.enableUserInput();
149                         if (zoom > 0)
150                         {
151                                 canvas.moveTo(offX, offY, zoom);
152                                 canvas.commitTransform();
153                         }
154
155                         AssignableMicroInstructionMemory mIMemory = machine.getMicroInstructionMemory();
156                         instPreview.bindMicroInstructionMemory(mIMemory);
157                         mIMemory.registerCellModifiedListener(memCellListener);
158
159                         canvasParent.layout();
160
161                         // initialize executer
162                         exec = new LogicExecuter(machine.getTimeline());
163                         updateSpeedFactorFromScale();
164                         updatePausedState();
165                         exec.startLiveExecution();
166                 } else
167                 {
168                         noMachineLabel.setVisible(true);
169                         resetButton.setEnabled(false);
170                         sbseButton.setEnabled(false);
171                         pauseButton.setEnabled(false);
172                         simSpeedScale.setEnabled(false);
173                         simSpeedInput.setEnabled(false);
174                 }
175         }
176
177         private void stopExecAndDeregisterContextDependentListeners()
178         {
179                 if (exec != null)
180                         exec.stopLiveExecution();
181                 if (machine != null)
182                 {
183                         machine.getMicroInstructionMemory().deregisterCellModifiedListener(memCellListener);
184                         machine.getClock().deregisterObserver(clockObserver);
185                 }
186         }
187
188         private void addSimulationControlWidgets(Composite parent)
189         {
190                 Composite c = new Composite(parent, SWT.NONE);
191                 c.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
192                 c.setLayout(new GridLayout(6, false));
193
194                 resetButton = new Button(c, SWT.PUSH);
195                 resetButton.setText("Reset machine");
196                 resetButton.addListener(SWT.Selection, e -> context.getActiveMachine().get().reset());
197
198                 sbseButton = new Button(c, SWT.CHECK);
199                 pauseButton = new Button(c, SWT.TOGGLE);
200
201                 sbseButton.setText("Step by step execution");
202                 sbseButton.addListener(SWT.Selection, e ->
203                 {
204                         CoreClock cl = machine.getClock();
205                         if (sbseButton.getSelection())
206                                 cl.registerObserver(clockObserver);
207                         else
208                                 cl.deregisterObserver(clockObserver);
209                 });
210                 sbseButton.setSelection(false);
211
212                 pauseButton.setSelection(true);
213                 setPauseText(pauseButton, false);
214
215                 pauseButton.addListener(SWT.Selection, e -> updatePausedState());
216                 pauseButton.addMouseTrackListener(new MouseTrackListener()
217                 {
218                         @Override
219                         public void mouseHover(MouseEvent e)
220                         {
221                                 // nothing
222                         }
223
224                         @Override
225                         public void mouseExit(MouseEvent e)
226                         {
227                                 setPauseText(pauseButton, false);
228                         }
229
230                         @Override
231                         public void mouseEnter(MouseEvent e)
232                         {
233                                 setPauseText(pauseButton, true);
234                         }
235                 });
236
237                 new Label(c, SWT.NONE).setText("Simulation Speed: ");
238
239                 simSpeedScale = new Scale(c, SWT.NONE);
240                 simSpeedScale.setMinimum(0);
241                 simSpeedScale.setMaximum(SIM_SPEED_SCALE_STEPS);
242                 simSpeedScale.setIncrement(1);
243                 simSpeedScale.setSelection(0);
244                 simSpeedScale.addListener(SWT.Selection, e -> updateSpeedFactorFromScale());
245
246                 simSpeedInput = new DoubleInput(c, SWT.NONE);
247                 simSpeedInput.setPrecision(Preferences.current().getInt("net.mograsim.plugin.core.simspeedprecision"));
248                 simSpeedInput.addChangeListener(this::updateSpeedFactorFromInput);
249
250                 updateSpeedFactorFromScale();
251
252                 c.layout();
253         }
254
255         private void updatePausedState()
256         {
257                 setPauseText(pauseButton, false);
258                 if (exec != null)
259                         if (pauseButton.getSelection())
260                                 exec.unpauseLiveExecution();
261                         else
262                                 exec.pauseLiveExecution();
263         }
264
265         private void updateSpeedFactorFromScale()
266         {
267                 double factor = Math.pow(SIM_SPEED_SCALE_STEP_FACTOR, simSpeedScale.getSelection() - SIM_SPEED_SCALE_STEPS);
268                 simSpeedInput.setValue(factor);
269                 if (exec != null)
270                         exec.setSpeedFactor(factor);
271         }
272
273         private void updateSpeedFactorFromInput(double factor)
274         {
275                 double factorCheckedFor0;
276                 if (factor != 0)
277                         factorCheckedFor0 = factor;
278                 else
279                 {
280                         factorCheckedFor0 = Math.pow(10, -simSpeedInput.getPrecision());
281                         simSpeedInput.setValue(factorCheckedFor0);
282                 }
283                 int closestScalePos = (int) Math.round(Math.log(factorCheckedFor0) / SIM_SPEED_SCALE_STEP_FACTOR_LOG + SIM_SPEED_SCALE_STEPS);
284                 simSpeedScale.setSelection(Math.min(Math.max(closestScalePos, 0), SIM_SPEED_SCALE_STEPS));
285                 if (exec != null)
286                         exec.setSpeedFactor(factorCheckedFor0);
287         }
288
289         private void addInstructionPreviewControlWidgets(Composite parent)
290         {
291                 instPreview = new InstructionTable(parent, new DisplaySettings());
292                 instPreview.getTableViewer().getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
293                 instPreview.setContentProvider(new ActiveInstructionPreviewContentProvider(instPreview.getTableViewer()));
294         }
295
296         private static void setPauseText(Button pauseButton, boolean hovered)
297         {
298                 if (hovered)
299                         if (pauseButton.getSelection())
300                                 pauseButton.setText("Pause?");
301                         else
302                                 pauseButton.setText("Resume?");
303                 else if (pauseButton.getSelection())
304                         pauseButton.setText("Running");
305                 else
306                         pauseButton.setText("Paused");
307         }
308
309         @Override
310         public void init(IEditorSite site, IEditorInput input) throws PartInitException
311         {
312                 if (input instanceof IFileEditorInput)
313                 {
314                         IFileEditorInput fileInput = (IFileEditorInput) input;
315                         context = ProjectMachineContext.getMachineContextOf(fileInput.getFile().getProject());
316                         context.activateMachine();
317                         context.addActiveMachineListener(activeMachineListener);
318                         recreateContextDependentControls();
319
320                         setPartName(fileInput.getName());
321                         open(fileInput.getFile());
322                 } else
323                         throw new IllegalArgumentException("SimulationViewEditor can only be used with Files");
324
325                 setSite(site);
326                 setInput(input);
327         }
328
329         @Override
330         public void doSave(IProgressMonitor monitor)
331         {
332                 IEditorInput input = getEditorInput();
333                 if (input instanceof IFileEditorInput)
334                         SafeRunnable.getRunner().run(() -> save(((IFileEditorInput) input).getFile(), monitor));
335                 else
336                         throw new IllegalArgumentException("SimulationViewEditor can only be used with Files");
337         }
338
339         private void save(IFile file, IProgressMonitor monitor) throws CoreException
340         {
341                 file.setContents(new ByteArrayInputStream("actual contents will go here".getBytes()), 0, monitor);
342         }
343
344         private void open(IFile file)
345         {
346                 // do nothing yet
347         }
348
349         @Override
350         public void doSaveAs()
351         {
352                 throw new UnsupportedOperationException();
353         }
354
355         @Override
356         public boolean isDirty()
357         {
358                 return false;
359         }
360
361         @Override
362         public boolean isSaveAsAllowed()
363         {
364                 return false;
365         }
366
367         @Override
368         public void setFocus()
369         {
370                 canvas.setFocus();
371         }
372
373         @Override
374         public void dispose()
375         {
376                 stopExecAndDeregisterContextDependentListeners();
377                 context.removeActiveMachineListener(activeMachineListener);
378                 super.dispose();
379         }
380 }