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