15346c63412b4ddb867e9bdaa14bd8952670093f
[Mograsim.git] / 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.model.components.submodels.SubmodelInterface;
31 import net.mograsim.logic.model.model.wires.ModelWireCrossPoint;
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 static final boolean OPEN_DEBUG_SETHIGHLEVELSTATE_SHELL = false;
42
43         private final LogicModel model;
44
45         public LogicUICanvas(Composite parent, int style, LogicModel model)
46         {
47                 super(parent, style, Preferences.current().getBoolean("net.mograsim.logic.model.improvetext"));
48
49                 this.model = model;
50
51                 LogicUIRenderer renderer = new LogicUIRenderer(model);
52                 addZoomedRenderer(gc ->
53                 {
54                         Color background = Preferences.current().getColor("net.mograsim.logic.model.color.background");
55                         if (background != null)
56                                 setBackground(background);// this.setBackground, not gc.setBackground to have the background fill the canvas
57                         renderer.render(gc, new Rectangle(-offX / zoom, -offY / zoom, gW / zoom, gH / zoom));
58                 });
59                 model.setRedrawHandler(this::redrawThreadsafe);
60
61                 addListener(SWT.MouseDown, this::mouseDown);
62
63                 if (OPEN_DEBUG_SETHIGHLEVELSTATE_SHELL)
64                         openDebugSetHighLevelStateShell(model);
65         }
66
67         private void mouseDown(Event e)
68         {
69                 if (e.button == 1)
70                 {
71                         Point click = canvasToWorldCoords(e.x, e.y);
72                         for (ModelComponent component : model.getComponentsByName().values())
73                                 if (component.getBounds().contains(click) && component.clicked(click.x, click.y))
74                                 {
75                                         redraw();
76                                         break;
77                                 }
78                 }
79         }
80
81         private void openDebugSetHighLevelStateShell(LogicModel model)
82         {
83                 Shell debugShell = new Shell();
84                 debugShell.setLayout(new GridLayout(2, false));
85                 new Label(debugShell, SWT.NONE).setText("Target component: ");
86                 Combo componentSelector = new Combo(debugShell, SWT.DROP_DOWN | SWT.READ_ONLY);
87                 componentSelector.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
88                 List<ModelComponent> componentsByItemIndex = new ArrayList<>();
89                 List<LogicModel> models = new ArrayList<>();
90                 AtomicBoolean recalculateQueued = new AtomicBoolean();
91                 AtomicReference<Consumer<? super ModelComponent>> compAdded = new AtomicReference<>();
92                 AtomicReference<Consumer<? super ModelComponent>> compRemoved = new AtomicReference<>();
93                 compAdded.set(c -> compsChanged(compAdded.get(), compRemoved.get(), c, models, componentsByItemIndex, componentSelector, model,
94                                 recalculateQueued, true));
95                 compRemoved.set(c -> compsChanged(compAdded.get(), compRemoved.get(), c, models, componentsByItemIndex, componentSelector, model,
96                                 recalculateQueued, false));
97                 iterateModelTree(compAdded.get(), compRemoved.get(), model, models, true);
98                 debugShell.addListener(SWT.Dispose, e -> models.forEach(m ->
99                 {
100                         m.removeComponentAddedListener(compAdded.get());
101                         m.removeComponentRemovedListener(compRemoved.get());
102                 }));
103                 queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model);
104                 new Label(debugShell, SWT.NONE).setText("Target state ID: ");
105                 Text stateIDText = new Text(debugShell, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
106                 stateIDText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
107                 new Label(debugShell, SWT.NONE).setText("Value type: ");
108                 Composite radioGroup = new Composite(debugShell, SWT.NONE);
109                 radioGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
110                 GridLayout radioGroupLayout = new GridLayout(2, false);
111                 radioGroupLayout.marginHeight = 0;
112                 radioGroupLayout.marginWidth = 0;
113                 radioGroup.setLayout(radioGroupLayout);
114                 Button radioBit = new Button(radioGroup, SWT.RADIO);
115                 radioBit.setText("Single bit");
116                 Button radioBitVector = new Button(radioGroup, SWT.RADIO);
117                 radioBitVector.setText("Bitvector");
118                 new Label(debugShell, SWT.NONE).setText("Value string representation: \n(Bit vectors: MSBit...LSBit)");
119                 Text valueText = new Text(debugShell, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
120                 valueText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
121                 Button send = new Button(debugShell, SWT.PUSH);
122                 send.setText("Send!");
123                 Button get = new Button(debugShell, SWT.PUSH);
124                 get.setText("Get!");
125                 Text output = new Text(debugShell, SWT.READ_ONLY);
126                 output.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
127                 Listener sendAction = e ->
128                 {
129                         try
130                         {
131                                 int componentIndex = componentSelector.getSelectionIndex();
132                                 if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
133                                         throw new RuntimeException("No component selected");
134                                 ModelComponent target = componentsByItemIndex.get(componentIndex);
135                                 String valueString = valueText.getText();
136                                 Object value;
137                                 if (radioBit.getSelection())
138                                         value = Bit.parse(valueString);
139                                 else if (radioBitVector.getSelection())
140                                         value = BitVector.parse(valueString);
141                                 else
142                                         throw new RuntimeException("No value type selected");
143                                 target.setHighLevelState(stateIDText.getText(), value);
144                                 output.setText("Success!");
145                         }
146                         catch (Exception x)
147                         {
148                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
149                         }
150                 };
151                 Listener getAction = e ->
152                 {
153                         try
154                         {
155                                 if (componentSelector.getSelectionIndex() >= componentsByItemIndex.size())
156                                         throw new RuntimeException("No valid component selected");
157                                 output.setText("Success! Value: \r\n"
158                                                 + componentsByItemIndex.get(componentSelector.getSelectionIndex()).getHighLevelState(stateIDText.getText()));
159                         }
160                         catch (Exception x)
161                         {
162                                 output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
163                         }
164                 };
165                 send.addListener(SWT.Selection, sendAction);
166                 valueText.addListener(SWT.DefaultSelection, sendAction);
167                 get.addListener(SWT.Selection, getAction);
168                 stateIDText.addListener(SWT.DefaultSelection, getAction);
169                 debugShell.open();
170         }
171
172         private void compsChanged(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved, ModelComponent c,
173                         List<LogicModel> models, List<ModelComponent> componentsByItemIndex, Combo componentSelector, LogicModel model,
174                         AtomicBoolean recalculateQueued, boolean add)
175         {
176                 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
177                 queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model);
178         }
179
180         private void iterateSubmodelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
181                         ModelComponent c, List<LogicModel> models, boolean add)
182         {
183                 if (c instanceof SubmodelComponent)
184                         iterateModelTree(compAdded, compRemoved, ((SubmodelComponent) c).submodel, models, add);
185         }
186
187         private void iterateModelTree(Consumer<? super ModelComponent> compAdded, Consumer<? super ModelComponent> compRemoved,
188                         LogicModel model, List<LogicModel> models, boolean add)
189         {
190                 if (add ^ models.contains(model))
191                 {
192                         if (add)
193                         {
194                                 models.add(model);
195                                 model.addComponentAddedListener(compAdded);
196                                 model.addComponentRemovedListener(compRemoved);
197                         } else
198                         {
199                                 models.remove(model);
200                                 model.removeComponentAddedListener(compAdded);
201                                 model.removeComponentRemovedListener(compRemoved);
202                         }
203                         for (ModelComponent c : model.getComponentsByName().values())
204                                 iterateSubmodelTree(compAdded, compRemoved, c, models, add);
205                 }
206         }
207
208         private void queueRecalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
209                         Combo componentSelector, LogicModel model)
210         {
211                 if (recalculateQueued.compareAndSet(false, true))
212                         getDisplay().asyncExec(() -> recalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model));
213         }
214
215         private void recalculateComponentSelector(AtomicBoolean recalculateQueued, List<ModelComponent> componentsByItemIndex,
216                         Combo componentSelector, LogicModel model)
217         {
218                 recalculateQueued.set(false);
219                 componentsByItemIndex.clear();
220                 componentSelector.setItems();
221                 addComponentSelectorItems(componentsByItemIndex, "", componentSelector, model);
222         }
223
224         private void addComponentSelectorItems(List<ModelComponent> componentsByItemIndex, String base, Combo componentSelector,
225                         LogicModel model)
226         {
227                 model.getComponentsByName().values().stream().sorted((c1, c2) -> c1.getName().compareTo(c2.getName())).forEach(c ->
228                 {
229                         if (!(c instanceof ModelWireCrossPoint || c instanceof SubmodelInterface))
230                         {
231                                 String item = base + c.getName();
232                                 componentsByItemIndex.add(c);
233                                 componentSelector.add(item);
234                                 if (c instanceof SubmodelComponent)
235                                         addComponentSelectorItems(componentsByItemIndex, item + " -> ", componentSelector, ((SubmodelComponent) c).submodel);
236                         }
237                 });
238         }
239 }