Some work on improving BitVector<->String conversions
[Mograsim.git] / plugins / net.mograsim.logic.model / src / net / mograsim / logic / model / LogicUICanvas.java
1 package net.mograsim.logic.model;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.concurrent.atomic.AtomicBoolean;
8 import java.util.function.Consumer;
9
10 import org.eclipse.swt.SWT;
11 import org.eclipse.swt.graphics.Color;
12 import org.eclipse.swt.layout.GridData;
13 import org.eclipse.swt.layout.GridLayout;
14 import org.eclipse.swt.widgets.Button;
15 import org.eclipse.swt.widgets.Combo;
16 import org.eclipse.swt.widgets.Composite;
17 import org.eclipse.swt.widgets.Event;
18 import org.eclipse.swt.widgets.Label;
19 import org.eclipse.swt.widgets.Listener;
20 import org.eclipse.swt.widgets.Shell;
21 import org.eclipse.swt.widgets.Text;
22
23 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
24 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
25 import net.haspamelodica.swt.helper.zoomablecanvas.ZoomableCanvas;
26 import net.mograsim.logic.core.types.Bit;
27 import net.mograsim.logic.core.types.BitVector;
28 import net.mograsim.logic.core.types.BitVectorFormatter;
29 import net.mograsim.logic.model.model.LogicModel;
30 import net.mograsim.logic.model.model.components.ModelComponent;
31 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
32 import net.mograsim.logic.model.snippets.highlevelstatehandlers.DefaultHighLevelStateHandler;
33 import net.mograsim.preferences.Preferences;
34
35 /**
36  * Simulation visualizer canvas.
37  * 
38  * @author Daniel Kirschten
39  */
40 public class LogicUICanvas extends ZoomableCanvas
41 {
42         private final LogicModel model;
43
44         public LogicUICanvas(Composite parent, int style, LogicModel model)
45         {
46                 super(parent, style, Preferences.current().getBoolean("net.mograsim.logic.model.improvetext"));
47
48                 this.model = model;
49
50                 LogicUIRenderer renderer = new LogicUIRenderer(model);
51                 addZoomedRenderer(gc ->
52                 {
53                         Color background = Preferences.current().getColor("net.mograsim.logic.model.color.background");
54                         if (background != null)
55                                 setBackground(background);// this.setBackground, not gc.setBackground to have the background fill the canvas
56                         renderer.render(gc, new Rectangle(-offX / zoom, -offY / zoom, gW / zoom, gH / zoom));
57                 });
58                 model.setRedrawHandler(() ->
59                 {
60                         if (!isDisposed())
61                                 redrawThreadsafe();
62                 });
63
64                 addListener(SWT.MouseDown, this::mouseDown);
65
66                 if (Preferences.current().getBoolean("net.mograsim.logic.model.debug.openhlsshell"))
67                         openDebugSetHighLevelStateShell(model);
68         }
69
70         private void mouseDown(Event e)
71         {
72                 if (e.button == Preferences.current().getInt("net.mograsim.logic.model.button.action"))
73                 {
74                         Point click = canvasToWorldCoords(e.x, e.y);
75                         for (ModelComponent component : model.getComponentsByName().values())
76                                 if (component.getBounds().contains(click) && component.clicked(click.x, click.y))
77                                 {
78                                         redraw();
79                                         break;
80                                 }
81                 }
82         }
83
84         private void openDebugSetHighLevelStateShell(LogicModel model)
85         {
86                 Shell debugShell = new Shell();
87                 debugShell.setLayout(new GridLayout(2, false));
88                 new Label(debugShell, SWT.NONE).setText("Target component: ");
89                 Combo componentSelector = new Combo(debugShell, SWT.DROP_DOWN | SWT.READ_ONLY);
90                 componentSelector.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
91                 List<ModelComponent> componentsByItemIndex = new ArrayList<>();
92                 List<LogicModel> models = new ArrayList<>();
93                 AtomicBoolean recalculateQueued = new AtomicBoolean();
94                 @SuppressWarnings("unchecked")
95                 Consumer<? super ModelComponent>[] compAdded = new Consumer[1];
96                 @SuppressWarnings("unchecked")
97                 Consumer<? super ModelComponent>[] compRemoved = new Consumer[1];
98                 compAdded[0] = c -> compsChanged(compAdded[0], compRemoved[0], c, models, componentsByItemIndex, componentSelector, model,
99                                 recalculateQueued, true);
100                 compRemoved[0] = c -> compsChanged(compAdded[0], compRemoved[0], c, models, componentsByItemIndex, componentSelector, model,
101                                 recalculateQueued, false);
102                 iterateModelTree(compAdded[0], compRemoved[0], model, models, true);
103                 debugShell.addListener(SWT.Dispose, e -> models.forEach(m ->
104                 {
105                         m.removeComponentAddedListener(compAdded[0]);
106                         m.removeComponentRemovedListener(compRemoved[0]);
107                 }));
108                 queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model);
109                 new Label(debugShell, SWT.NONE).setText("Target state ID: ");
110                 Text stateIDText = new Text(debugShell, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
111                 stateIDText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
112                 new Label(debugShell, SWT.NONE).setText("Value type: ");
113                 Composite radioGroup = new Composite(debugShell, SWT.NONE);
114                 radioGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
115                 GridLayout radioGroupLayout = new GridLayout(2, false);
116                 radioGroupLayout.marginHeight = 0;
117                 radioGroupLayout.marginWidth = 0;
118                 radioGroup.setLayout(radioGroupLayout);
119                 Button radioBit = new Button(radioGroup, SWT.RADIO);
120                 radioBit.setText("Single bit");
121                 Button radioBitVector = new Button(radioGroup, SWT.RADIO);
122                 radioBitVector.setText("Bitvector");
123                 new Label(debugShell, SWT.NONE).setText("Value string representation: \n(Bit vectors: MSBit...LSBit)");
124                 Text valueText = new Text(debugShell, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
125                 valueText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
126                 Button send = new Button(debugShell, SWT.PUSH);
127                 send.setText("Send!");
128                 Button addListener = new Button(debugShell, SWT.PUSH);
129                 addListener.setText("Add sysout listener");
130                 Button get = new Button(debugShell, SWT.PUSH);
131                 get.setText("Get!");
132                 Button removeListener = new Button(debugShell, SWT.PUSH);
133                 removeListener.setText("Remove sysout listener");
134                 Text output = new Text(debugShell, SWT.READ_ONLY);
135                 output.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
136                 Listener sendAction = e ->
137                 {
138                         try
139                         {
140                                 int componentIndex = componentSelector.getSelectionIndex();
141                                 if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
142                                         throw new RuntimeException("No component selected");
143                                 ModelComponent target = componentsByItemIndex.get(componentIndex);
144                                 String valueString = valueText.getText();
145                                 String stateID = stateIDText.getText();
146                                 Object value;
147                                 if (radioBit.getSelection())
148                                         value = Bit.parse(valueString);
149                                 else if (radioBitVector.getSelection())
150                                 {
151                                         Object hls = target.getHighLevelState(stateID);
152                                         int width;
153                                         if (hls instanceof Bit)
154                                                 width = 1;
155                                         else if (hls instanceof BitVector)
156                                                 width = ((BitVector) hls).length();
157                                         else
158                                                 width = -1;
159                                         value = BitVectorFormatter.parseUserBitVector(valueString, width);
160                                 } else
161                                         throw new RuntimeException("No value type selected");
162                                 target.setHighLevelState(stateID, value);
163                                 output.setText("Success!");
164                         }
165                         catch (Exception x)
166                         {
167                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
168                         }
169                 };
170                 Listener getAction = e ->
171                 {
172                         try
173                         {
174                                 int componentIndex = componentSelector.getSelectionIndex();
175                                 if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
176                                         throw new RuntimeException("No component selected");
177                                 output.setText("Success! Value: \r\n"
178                                                 + componentsByItemIndex.get(componentSelector.getSelectionIndex()).getHighLevelState(stateIDText.getText()));
179                         }
180                         catch (Exception x)
181                         {
182                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
183                         }
184                 };
185                 send.addListener(SWT.Selection, sendAction);
186                 valueText.addListener(SWT.DefaultSelection, sendAction);
187                 get.addListener(SWT.Selection, getAction);
188                 stateIDText.addListener(SWT.DefaultSelection, getAction);
189                 Map<ModelComponent, Map<String, Consumer<Object>>> sysoutListenersPerHLSPerTarget = new HashMap<>();
190                 addListener.addListener(SWT.Selection, e ->
191                 {
192                         try
193                         {
194                                 int componentIndex = componentSelector.getSelectionIndex();
195                                 if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
196                                         throw new RuntimeException("No component selected");
197                                 ModelComponent target = componentsByItemIndex.get(componentIndex);
198                                 Map<String, Consumer<Object>> sysoutListenersPerHLS = sysoutListenersPerHLSPerTarget.computeIfAbsent(target,
199                                                 k -> new HashMap<>());
200                                 String stateIDString = stateIDText.getText();
201                                 if (sysoutListenersPerHLS.containsKey(stateIDString))
202                                         throw new RuntimeException("Listener already registered");
203                                 Consumer<Object> sysoutListener = v -> System.out.println(stateIDString + ": " + v);
204                                 target.addHighLevelStateListener(stateIDString, sysoutListener);
205                                 sysoutListenersPerHLS.put(stateIDString, sysoutListener);
206                                 output.setText("Success!");
207                         }
208                         catch (Exception x)
209                         {
210                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
211                         }
212                 });
213                 removeListener.addListener(SWT.Selection, e ->
214                 {
215                         try
216                         {
217                                 int componentIndex = componentSelector.getSelectionIndex();
218                                 if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
219                                         throw new RuntimeException("No component selected");
220                                 ModelComponent target = componentsByItemIndex.get(componentIndex);
221                                 Map<String, Consumer<Object>> sysoutListenersPerHLS = sysoutListenersPerHLSPerTarget.get(target);
222                                 if (sysoutListenersPerHLS == null)
223                                         throw new RuntimeException("Listener not registered");
224                                 String stateIDString = stateIDText.getText();
225                                 Consumer<Object> sysoutListener = sysoutListenersPerHLS.remove(stateIDString);
226                                 if (sysoutListener == null)
227                                         throw new RuntimeException("Listener not registered");
228                                 target.removeHighLevelStateListener(stateIDString, sysoutListener);
229                                 output.setText("Success!");
230                         }
231                         catch (Exception x)
232                         {
233                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
234                         }
235                 });
236                 debugShell.open();
237                 addDisposeListener(e -> debugShell.dispose());
238         }
239
240         private void compsChanged(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved, ModelComponent c,
241                         List<LogicModel> models, List<ModelComponent> componentsByItemIndex, Combo componentSelector, LogicModel model,
242                         AtomicBoolean recalculateQueued, boolean add)
243         {
244                 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
245                 queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model);
246         }
247
248         private void iterateSubmodelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
249                         ModelComponent c, List<LogicModel> models, boolean add)
250         {
251                 if (c instanceof SubmodelComponent)
252                         iterateModelTree(compAdded, compRemoved, ((SubmodelComponent) c).submodel, models, add);
253         }
254
255         private void iterateModelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
256                         LogicModel model, List<LogicModel> models, boolean add)
257         {
258                 if (add ^ models.contains(model))
259                 {
260                         if (add)
261                         {
262                                 models.add(model);
263                                 model.addComponentAddedListener(compAdded);
264                                 model.addComponentRemovedListener(compRemoved);
265                         } else
266                         {
267                                 models.remove(model);
268                                 model.removeComponentAddedListener(compAdded);
269                                 model.removeComponentRemovedListener(compRemoved);
270                         }
271                         for (ModelComponent c : model.getComponentsByName().values())
272                                 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
273                 }
274         }
275
276         private void queueRecalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
277                         Combo componentSelector, LogicModel model)
278         {
279                 if (recalculateQueued.compareAndSet(false, true))
280                         getDisplay().asyncExec(() -> recalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model));
281         }
282
283         private void recalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
284                         Combo componentSelector, LogicModel model)
285         {
286                 recalculateQueued.set(false);
287                 componentsByItemIndex.clear();
288                 componentSelector.setItems();
289                 addComponentSelectorItems(componentsByItemIndex, "", componentSelector, model,
290                                 Preferences.current().getInt("net.mograsim.logic.model.debug.hlsshelldepth") - 1);
291         }
292
293         private void addComponentSelectorItems(List<ModelComponent> componentsByItemIndex, String base, Combo componentSelector,
294                         LogicModel model, int depth)
295         {
296                 model.getComponentsByName().values().stream().sorted((c1, c2) -> c1.getName().compareTo(c2.getName())).forEach(c ->
297                 {
298                         if (!(c.getHighLevelStateHandler() instanceof DefaultHighLevelStateHandler))
299                         {
300                                 String item = base + c.getName();
301                                 componentsByItemIndex.add(c);
302                                 componentSelector.add(item);
303                                 // this causes negative numbers to result in infinite depth
304                                 if (depth != 0 && c instanceof SubmodelComponent)
305                                         addComponentSelectorItems(componentsByItemIndex, item + " -> ", componentSelector, ((SubmodelComponent) c).submodel,
306                                                         depth - 1);
307                         }
308                 });
309         }
310 }