MPROMEditor now calls its columns "Opcode" and "muPC"
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / editors / AbstractMemoryEditor.java
1 package net.mograsim.plugin.editors;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.math.BigInteger;
6 import java.util.Collection;
7 import java.util.HashSet;
8
9 import org.eclipse.core.resources.IFile;
10 import org.eclipse.core.runtime.CoreException;
11 import org.eclipse.core.runtime.IProgressMonitor;
12 import org.eclipse.jface.util.IPropertyChangeListener;
13 import org.eclipse.jface.util.SafeRunnable;
14 import org.eclipse.jface.viewers.TableViewerColumn;
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.graphics.Font;
17 import org.eclipse.swt.layout.GridData;
18 import org.eclipse.swt.layout.GridLayout;
19 import org.eclipse.swt.widgets.Button;
20 import org.eclipse.swt.widgets.Composite;
21 import org.eclipse.swt.widgets.Control;
22 import org.eclipse.swt.widgets.Label;
23 import org.eclipse.swt.widgets.ScrollBar;
24 import org.eclipse.swt.widgets.Table;
25 import org.eclipse.swt.widgets.TableColumn;
26 import org.eclipse.swt.widgets.Text;
27 import org.eclipse.ui.IEditorInput;
28 import org.eclipse.ui.IEditorSite;
29 import org.eclipse.ui.IFileEditorInput;
30 import org.eclipse.ui.PartInitException;
31 import org.eclipse.ui.part.EditorPart;
32 import org.eclipse.ui.themes.ITheme;
33 import org.eclipse.ui.themes.IThemeManager;
34
35 import net.mograsim.machine.BitVectorMemory;
36 import net.mograsim.machine.MachineDefinition;
37 import net.mograsim.machine.Memory.MemoryCellModifiedListener;
38 import net.mograsim.machine.mi.MicroInstructionMemoryParseException;
39 import net.mograsim.machine.standard.memory.BitVectorBasedMemoryParser;
40 import net.mograsim.plugin.asm.AsmNumberUtil;
41 import net.mograsim.plugin.nature.MachineContext;
42 import net.mograsim.plugin.nature.ProjectMachineContext;
43 import net.mograsim.plugin.tables.AddressLabelProvider;
44 import net.mograsim.plugin.tables.DisplaySettings;
45 import net.mograsim.plugin.tables.LazyTableViewer;
46 import net.mograsim.plugin.tables.NumberColumnLabelProvider;
47 import net.mograsim.plugin.tables.RadixSelector;
48 import net.mograsim.plugin.tables.memory.MemoryCellEditingSupport;
49 import net.mograsim.plugin.tables.memory.MemoryTableContentProvider;
50 import net.mograsim.plugin.tables.memory.MemoryTableRow;
51 import net.mograsim.plugin.tables.memory.NumberVerifyListener;
52
53 public abstract class AbstractMemoryEditor extends EditorPart
54 {
55         private MachineContext context;
56
57         private BitVectorMemory memory;
58
59         private LazyTableViewer viewer;
60         private MemoryTableContentProvider provider;
61         private DisplaySettings displaySettings;
62         private final String addrColName;
63         private final String dataColName;
64
65         private Collection<Control> fontDependent = new HashSet<>();
66
67         private boolean dirty;
68
69         private final MemoryCellModifiedListener memListener;
70
71         private final static String font = "net.mograsim.plugin.memory.table_font";
72         private IPropertyChangeListener fontChangeListener;
73
74         public AbstractMemoryEditor(String addrColName, String dataColName)
75         {
76                 this.addrColName = addrColName;
77                 this.dataColName = dataColName;
78
79                 memListener = this::cellModified;
80         }
81
82         @Override
83         public void createPartControl(Composite parent)
84         {
85                 provider = new MemoryTableContentProvider();
86                 displaySettings = new DisplaySettings();
87
88                 parent.setLayout(new GridLayout(8, false));
89                 createHeader(parent);
90                 createViewer(parent);
91
92                 displaySettings.addObserver(() -> viewer.refresh());
93         }
94
95         @SuppressWarnings("unused") // RadixSelector and exceptions
96         private void createHeader(Composite parent)
97         {
98                 Text gotoText = new Text(parent, SWT.BORDER);
99                 gotoText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
100                 NumberVerifyListener vl = new NumberVerifyListener();
101                 gotoText.addVerifyListener(vl);
102                 gotoText.setText("0");
103
104                 Runnable gotoAction = () ->
105                 {
106                         try
107                         {
108                                 loadAround((int) (AsmNumberUtil.valueOf(gotoText.getText()).longValue() - provider.getLowerBound()));
109                                 viewer.getTable().deselectAll();
110                         }
111                         catch (NumberFormatException x)
112                         {
113                                 // Nothing to do here
114                         }
115                 };
116
117                 gotoText.addTraverseListener((e) ->
118                 {
119                         if (e.detail == SWT.TRAVERSE_RETURN)
120                                 gotoAction.run();
121                 });
122
123                 Button gotoButton = new Button(parent, SWT.PUSH);
124                 gotoButton.setText("Go to");
125                 gotoButton.addListener(SWT.Selection, e ->
126                 {
127                         gotoAction.run();
128                 });
129
130                 new Label(parent, SWT.NONE).setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
131
132                 new RadixSelector(parent, displaySettings);
133         }
134
135         private void createViewer(Composite parent)
136         {
137                 viewer = new LazyTableViewer(parent, SWT.FULL_SELECTION | SWT.BORDER | SWT.VIRTUAL);
138                 fontDependent.add(viewer.getTable());
139                 createColumns();
140                 Table table = viewer.getTable();
141                 table.setHeaderVisible(true);
142                 table.setLinesVisible(true);
143                 viewer.setUseHashlookup(true);
144                 viewer.setContentProvider(provider);
145                 getSite().setSelectionProvider(viewer);
146                 viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 8, 1));
147
148                 // TODO: Also support keyboard inputs for flexible scrolling
149                 ScrollBar vBar = table.getVerticalBar();
150                 vBar.addListener(SWT.Selection, (e) ->
151                 {
152                         int sel;
153                         if ((sel = vBar.getSelection()) < vBar.getMinimum() + vBar.getThumb() * 2 || sel > vBar.getMaximum() - vBar.getThumb() * 2)
154                         {
155                                 loadAround(table.getTopIndex());
156                                 table.deselectAll();
157                         }
158                 });
159
160                 IThemeManager themeManager = getSite().getWorkbenchWindow().getWorkbench().getThemeManager();
161                 themeManager.addPropertyChangeListener(fontChangeListener = (e) ->
162                 {
163                         if (IThemeManager.CHANGE_CURRENT_THEME.equals(e.getProperty()) || font.equals(e.getProperty()))
164                         {
165                                 updateFont(themeManager.getCurrentTheme());
166                                 viewer.refresh();
167                         }
168                 });
169                 updateFont(themeManager.getCurrentTheme());
170
171                 if (memory != null)
172                         viewer.setInput(memory);
173         }
174
175         private void updateFont(ITheme theme)
176         {
177                 Font newFont = theme.getFontRegistry().get(font);
178                 // TODO: This is a quick fix! Still have to figure out why the CellEditors do not get the appropriate Font on their own
179                 fontDependent.forEach(c -> c.setFont(newFont));
180         }
181
182         private void loadAround(int row)
183         {
184                 long prevLb = provider.getLowerBound();
185                 long address = prevLb + row;
186                 long actualLb = Long.max(address - MemoryTableContentProvider.MAX_VISIBLE_ROWS / 2, memory.getDefinition().getMinimalAddress());
187
188                 long prevHb = provider.getUpperBound();
189                 // +- 1 row is not really important
190                 long actualHb = Long.min(address + MemoryTableContentProvider.MAX_VISIBLE_ROWS / 2, memory.getDefinition().getMaximalAddress());
191
192                 if (actualLb != prevLb || actualHb != prevHb)
193                 {
194                         Table table = viewer.getTable();
195                         provider.setBounds(actualLb, actualHb);
196                         int rowIndex = (int) (address - actualLb);
197                         if (rowIndex >= 0 && rowIndex < table.getItemCount())
198                                 table.showItem(table.getItem(rowIndex));
199                         viewer.refresh();
200                 }
201         }
202
203         private void createColumns()
204         {
205                 TableViewerColumn addrCol = createTableViewerColumn(addrColName, 100);
206                 addrCol.setLabelProvider(new AddressLabelProvider());
207
208                 TableViewerColumn dataCol = createTableViewerColumn(dataColName, 100);
209                 dataCol.setLabelProvider(new NumberColumnLabelProvider(displaySettings)
210                 {
211                         @Override
212                         public BigInteger getAsBigInteger(Object element)
213                         {
214                                 MemoryTableRow row = (MemoryTableRow) element;
215                                 return row.getMemory().getCellAsBigInteger(row.address);
216                         }
217
218                         @Override
219                         public int getBitLength(Object element)
220                         {
221                                 return ((MemoryTableRow) element).getMemory().getDefinition().getCellWidth();
222                         }
223                 });
224                 MemoryCellEditingSupport eSup;
225                 dataCol.setEditingSupport(eSup = new MemoryCellEditingSupport(viewer, displaySettings));
226                 fontDependent.add(eSup.getCellEditorControl());
227         }
228
229         private TableViewerColumn createTableViewerColumn(String title, int width)
230         {
231                 TableViewerColumn viewerColumn = new TableViewerColumn(viewer, SWT.NONE);
232                 TableColumn column = viewerColumn.getColumn();
233                 column.setText(title);
234                 column.setWidth(width);
235                 column.setResizable(true);
236                 column.setMoveable(false);
237                 return viewerColumn;
238         }
239
240         @Override
241         public void init(IEditorSite site, IEditorInput input) throws PartInitException
242         {
243                 if (input instanceof IFileEditorInput)
244                 {
245                         IFileEditorInput fileInput = (IFileEditorInput) input;
246                         context = ProjectMachineContext.getMachineContextOf(fileInput.getFile().getProject());
247
248                         setPartName(fileInput.getName());
249                         try
250                         {
251                                 open(fileInput.getFile());
252                         }
253                         catch (Exception e)
254                         {
255                                 throw new PartInitException("Failed to read input!", e);
256                         }
257                 } else
258                         throw new IllegalArgumentException("MemoryEditor can only be used with Files");
259
260                 setSite(site);
261                 setInput(input);
262         }
263
264         @Override
265         public void doSave(IProgressMonitor monitor)
266         {
267                 IEditorInput input = getEditorInput();
268                 if (input instanceof IFileEditorInput)
269                         SafeRunnable.getRunner().run(() -> save(((IFileEditorInput) input).getFile(), monitor));
270         }
271
272         private void save(IFile file, IProgressMonitor monitor) throws CoreException, IOException
273         {
274                 if (memory == null)
275                 {
276                         throw new MicroInstructionMemoryParseException("Failed to write the memory to File. No memory assigned.");
277                 }
278                 try (InputStream toWrite = BitVectorBasedMemoryParser.write(memory))
279                 {
280                         file.setContents(toWrite, 0, monitor);
281                         setDirty(false);
282                 }
283         }
284
285         private void open(IFile file) throws IOException, CoreException
286         {
287                 MachineDefinition machDef = context.getMachineDefinition()
288                                 .orElseThrow(() -> new MicroInstructionMemoryParseException("No MachineDefinition assigned!"));
289                 memory = createEmptyMemory(machDef);
290                 BitVectorBasedMemoryParser.parseMemory(memory, file.getContents());
291                 memory.registerCellModifiedListener(memListener);
292                 if (viewer != null)
293                         viewer.setInput(memory);
294         }
295
296         protected abstract BitVectorMemory createEmptyMemory(MachineDefinition activeMachineDefinition);
297
298         private void cellModified(@SuppressWarnings("unused") long address)
299         {
300                 setDirty(true);
301         }
302
303         private void setDirty(boolean newDirty)
304         {
305                 dirty = newDirty;
306                 firePropertyChange(PROP_DIRTY);
307         }
308
309         @Override
310         public void doSaveAs()
311         {
312                 throw new UnsupportedOperationException();
313         }
314
315         @Override
316         public boolean isDirty()
317         {
318                 return dirty;
319         }
320
321         @Override
322         public boolean isSaveAsAllowed()
323         {
324                 return false;
325         }
326
327         @Override
328         public void setFocus()
329         {
330                 viewer.getTable().setFocus();
331         }
332
333         @Override
334         public void dispose()
335         {
336                 getSite().getWorkbenchWindow().getWorkbench().getThemeManager().removePropertyChangeListener(fontChangeListener);
337                 if (memory != null)
338                         memory.deregisterCellModifiedListener(memListener);
339                 super.dispose();
340         }
341 }