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