1 package net.mograsim.logic.model;
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;
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.List;
13 import java.util.concurrent.atomic.AtomicBoolean;
14 import java.util.function.Consumer;
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;
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;
41 * Simulation visualizer canvas.
43 * @author Daniel Kirschten
45 public class LogicUICanvas extends ZoomableCanvas
47 protected final LogicModel model;
48 protected final RenderPreferences renderPrefs;
50 public LogicUICanvas(Composite parent, int style, LogicModel model, RenderPreferences renderPrefs)
52 // TODO add a listener
53 super(parent, style, renderPrefs.getBoolean(IMPROVE_TEXT));
55 this.renderPrefs = renderPrefs;
58 // TODO add listeners for the render prefs
60 Color background = renderPrefs.getColor(BACKGROUND_COLOR);
61 if (background != null)
62 setBackground(background);
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(() ->
72 addListener(SWT.MouseDown, this::mouseDown);
74 if (renderPrefs.getBoolean(DEBUG_OPEN_HLSSHELL))
75 openDebugSetHighLevelStateShell(model, renderPrefs.getInt(DEBUG_HLSSHELL_DEPTH) - 1);
78 private void mouseDown(Event e)
80 if (e.button == renderPrefs.getInt(ACTION_BUTTON))
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))
92 private void openDebugSetHighLevelStateShell(LogicModel model, int depth)
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 ->
113 m.removeComponentAddedListener(compAdded[0]);
114 m.removeComponentRemovedListener(compRemoved[0]);
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);
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 ->
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();
155 if (radioBit.getSelection())
156 value = Bit.parse(valueString);
157 else if (radioBitVector.getSelection())
159 Object hls = target.getHighLevelState(stateID);
161 if (hls instanceof Bit)
163 else if (hls instanceof BitVector)
164 width = ((BitVector) hls).length();
167 value = BitVectorFormatter.parseUserBitVector(valueString, width);
169 throw new RuntimeException("No value type selected");
170 target.setHighLevelState(stateID, value);
171 output.setText("Success!");
175 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
178 Listener getAction = e ->
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()));
190 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
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 ->
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!");
218 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
221 removeListener.addListener(SWT.Selection, e ->
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!");
241 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
245 addDisposeListener(e -> debugShell.dispose());
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)
252 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
253 queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model, depth);
256 private void iterateSubmodelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
257 ModelComponent c, List<LogicModel> models, boolean add)
259 if (c instanceof SubmodelComponent)
260 iterateModelTree(compAdded, compRemoved, ((SubmodelComponent) c).submodel, models, add);
263 private void iterateModelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
264 LogicModel model, List<LogicModel> models, boolean add)
266 if (add ^ models.contains(model))
271 model.addComponentAddedListener(compAdded);
272 model.addComponentRemovedListener(compRemoved);
275 models.remove(model);
276 model.removeComponentAddedListener(compAdded);
277 model.removeComponentRemovedListener(compRemoved);
279 for (ModelComponent c : model.getComponentsByName().values())
280 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
284 private void queueRecalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
285 Combo componentSelector, LogicModel model, int depth)
287 if (recalculateQueued.compareAndSet(false, true))
288 getDisplay().asyncExec(
289 () -> recalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model, depth));
292 private void recalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
293 Combo componentSelector, LogicModel model, int depth)
295 recalculateQueued.set(false);
296 componentsByItemIndex.clear();
297 componentSelector.setItems();
298 addComponentSelectorItems(componentsByItemIndex, "", componentSelector, model, depth);
301 private void addComponentSelectorItems(List<ModelComponent> componentsByItemIndex, String base, Combo componentSelector,
302 LogicModel model, int depth)
304 model.getComponentsByName().values().stream().sorted((c1, c2) -> c1.getName().compareTo(c2.getName())).forEach(c ->
306 if (!(c.getHighLevelStateHandler() instanceof DefaultHighLevelStateHandler))
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,