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