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