Fixed annoying sporadic crashes
[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.List;
5 import java.util.concurrent.atomic.AtomicBoolean;
6 import java.util.concurrent.atomic.AtomicReference;
7 import java.util.function.Consumer;
8
9 import org.eclipse.swt.SWT;
10 import org.eclipse.swt.graphics.Color;
11 import org.eclipse.swt.layout.GridData;
12 import org.eclipse.swt.layout.GridLayout;
13 import org.eclipse.swt.widgets.Button;
14 import org.eclipse.swt.widgets.Combo;
15 import org.eclipse.swt.widgets.Composite;
16 import org.eclipse.swt.widgets.Event;
17 import org.eclipse.swt.widgets.Label;
18 import org.eclipse.swt.widgets.Listener;
19 import org.eclipse.swt.widgets.Shell;
20 import org.eclipse.swt.widgets.Text;
21
22 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
23 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
24 import net.haspamelodica.swt.helper.zoomablecanvas.ZoomableCanvas;
25 import net.mograsim.logic.core.types.Bit;
26 import net.mograsim.logic.core.types.BitVector;
27 import net.mograsim.logic.model.model.LogicModel;
28 import net.mograsim.logic.model.model.components.ModelComponent;
29 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
30 import net.mograsim.logic.model.snippets.highlevelstatehandlers.DefaultHighLevelStateHandler;
31 import net.mograsim.preferences.Preferences;
32
33 /**
34  * Simulation visualizer canvas.
35  * 
36  * @author Daniel Kirschten
37  */
38 public class LogicUICanvas extends ZoomableCanvas
39 {
40         private final LogicModel model;
41
42         public LogicUICanvas(Composite parent, int style, LogicModel model)
43         {
44                 super(parent, style, Preferences.current().getBoolean("net.mograsim.logic.model.improvetext"));
45
46                 this.model = model;
47
48                 LogicUIRenderer renderer = new LogicUIRenderer(model);
49                 addZoomedRenderer(gc ->
50                 {
51                         Color background = Preferences.current().getColor("net.mograsim.logic.model.color.background");
52                         if (background != null)
53                                 setBackground(background);// this.setBackground, not gc.setBackground to have the background fill the canvas
54                         renderer.render(gc, new Rectangle(-offX / zoom, -offY / zoom, gW / zoom, gH / zoom));
55                 });
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                 AtomicReference<Consumer<? super ModelComponent>> compAdded = new AtomicReference<>();
93                 AtomicReference<Consumer<? super ModelComponent>> compRemoved = new AtomicReference<>();
94                 compAdded.set(c -> compsChanged(compAdded.get(), compRemoved.get(), c, models, componentsByItemIndex, componentSelector, model,
95                                 recalculateQueued, true));
96                 compRemoved.set(c -> compsChanged(compAdded.get(), compRemoved.get(), c, models, componentsByItemIndex, componentSelector, model,
97                                 recalculateQueued, false));
98                 iterateModelTree(compAdded.get(), compRemoved.get(), model, models, true);
99                 debugShell.addListener(SWT.Dispose, e -> models.forEach(m ->
100                 {
101                         m.removeComponentAddedListener(compAdded.get());
102                         m.removeComponentRemovedListener(compRemoved.get());
103                 }));
104                 queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model);
105                 new Label(debugShell, SWT.NONE).setText("Target state ID: ");
106                 Text stateIDText = new Text(debugShell, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
107                 stateIDText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
108                 new Label(debugShell, SWT.NONE).setText("Value type: ");
109                 Composite radioGroup = new Composite(debugShell, SWT.NONE);
110                 radioGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
111                 GridLayout radioGroupLayout = new GridLayout(2, false);
112                 radioGroupLayout.marginHeight = 0;
113                 radioGroupLayout.marginWidth = 0;
114                 radioGroup.setLayout(radioGroupLayout);
115                 Button radioBit = new Button(radioGroup, SWT.RADIO);
116                 radioBit.setText("Single bit");
117                 Button radioBitVector = new Button(radioGroup, SWT.RADIO);
118                 radioBitVector.setText("Bitvector");
119                 new Label(debugShell, SWT.NONE).setText("Value string representation: \n(Bit vectors: MSBit...LSBit)");
120                 Text valueText = new Text(debugShell, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
121                 valueText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
122                 Button send = new Button(debugShell, SWT.PUSH);
123                 send.setText("Send!");
124                 Button get = new Button(debugShell, SWT.PUSH);
125                 get.setText("Get!");
126                 Text output = new Text(debugShell, SWT.READ_ONLY);
127                 output.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
128                 Listener sendAction = e ->
129                 {
130                         try
131                         {
132                                 int componentIndex = componentSelector.getSelectionIndex();
133                                 if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
134                                         throw new RuntimeException("No component selected");
135                                 ModelComponent target = componentsByItemIndex.get(componentIndex);
136                                 String valueString = valueText.getText();
137                                 Object value;
138                                 if (radioBit.getSelection())
139                                         value = Bit.parse(valueString);
140                                 else if (radioBitVector.getSelection())
141                                         value = BitVector.parse(valueString);
142                                 else
143                                         throw new RuntimeException("No value type selected");
144                                 target.setHighLevelState(stateIDText.getText(), value);
145                                 output.setText("Success!");
146                         }
147                         catch (Exception x)
148                         {
149                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
150                         }
151                 };
152                 Listener getAction = e ->
153                 {
154                         try
155                         {
156                                 if (componentSelector.getSelectionIndex() >= componentsByItemIndex.size())
157                                         throw new RuntimeException("No valid component selected");
158                                 output.setText("Success! Value: \r\n"
159                                                 + componentsByItemIndex.get(componentSelector.getSelectionIndex()).getHighLevelState(stateIDText.getText()));
160                         }
161                         catch (Exception x)
162                         {
163                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
164                         }
165                 };
166                 send.addListener(SWT.Selection, sendAction);
167                 valueText.addListener(SWT.DefaultSelection, sendAction);
168                 get.addListener(SWT.Selection, getAction);
169                 stateIDText.addListener(SWT.DefaultSelection, getAction);
170                 debugShell.open();
171                 addDisposeListener(e -> debugShell.dispose());
172         }
173
174         private void compsChanged(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved, ModelComponent c,
175                         List<LogicModel> models, List<ModelComponent> componentsByItemIndex, Combo componentSelector, LogicModel model,
176                         AtomicBoolean recalculateQueued, boolean add)
177         {
178                 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
179                 queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model);
180         }
181
182         private void iterateSubmodelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
183                         ModelComponent c, List<LogicModel> models, boolean add)
184         {
185                 if (c instanceof SubmodelComponent)
186                         iterateModelTree(compAdded, compRemoved, ((SubmodelComponent) c).submodel, models, add);
187         }
188
189         private void iterateModelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
190                         LogicModel model, List<LogicModel> models, boolean add)
191         {
192                 if (add ^ models.contains(model))
193                 {
194                         if (add)
195                         {
196                                 models.add(model);
197                                 model.addComponentAddedListener(compAdded);
198                                 model.addComponentRemovedListener(compRemoved);
199                         } else
200                         {
201                                 models.remove(model);
202                                 model.removeComponentAddedListener(compAdded);
203                                 model.removeComponentRemovedListener(compRemoved);
204                         }
205                         for (ModelComponent c : model.getComponentsByName().values())
206                                 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
207                 }
208         }
209
210         private void queueRecalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
211                         Combo componentSelector, LogicModel model)
212         {
213                 if (recalculateQueued.compareAndSet(false, true))
214                         getDisplay().asyncExec(() -> recalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model));
215         }
216
217         private void recalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
218                         Combo componentSelector, LogicModel model)
219         {
220                 recalculateQueued.set(false);
221                 componentsByItemIndex.clear();
222                 componentSelector.setItems();
223                 addComponentSelectorItems(componentsByItemIndex, "", componentSelector, model,
224                                 Preferences.current().getInt("net.mograsim.logic.model.debug.hlsshelldepth") - 1);
225         }
226
227         private void addComponentSelectorItems(List<ModelComponent> componentsByItemIndex, String base, Combo componentSelector,
228                         LogicModel model, int depth)
229         {
230                 model.getComponentsByName().values().stream().sorted((c1, c2) -> c1.getName().compareTo(c2.getName())).forEach(c ->
231                 {
232                         if (!(c.getHighLevelStateHandler() instanceof DefaultHighLevelStateHandler))
233                         {
234                                 String item = base + c.getName();
235                                 componentsByItemIndex.add(c);
236                                 componentSelector.add(item);
237                                 // this causes negative numbers to result in infinite depth
238                                 if (depth != 0 && c instanceof SubmodelComponent)
239                                         addComponentSelectorItems(componentsByItemIndex, item + " -> ", componentSelector, ((SubmodelComponent) c).submodel,
240                                                         depth - 1);
241                         }
242                 });
243         }
244 }