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