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