Restructured high level state access
[Mograsim.git] / net.mograsim.logic.ui / src / net / mograsim / logic / ui / model / components / SubmodelComponent.java
1 package net.mograsim.logic.ui.model.components;
2
3 import java.util.Collections;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Map.Entry;
10 import java.util.Set;
11
12 import net.haspamelodica.swt.helper.gcs.GCConfig;
13 import net.haspamelodica.swt.helper.gcs.GeneralGC;
14 import net.haspamelodica.swt.helper.gcs.TranslatedGC;
15 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
16 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
17 import net.mograsim.logic.ui.LogicUIRenderer;
18 import net.mograsim.logic.ui.model.ViewModel;
19 import net.mograsim.logic.ui.model.ViewModelModifiable;
20 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.ComponentCompositionParams;
21 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.ComponentCompositionParams.InnerComponentParams;
22 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.InnerPinParams;
23 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.InnerWireParams;
24 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.InterfacePinParams;
25 import net.mograsim.logic.ui.model.wires.GUIWire;
26 import net.mograsim.logic.ui.model.wires.MovablePin;
27 import net.mograsim.logic.ui.model.wires.Pin;
28
29 /**
30  * A {@link GUIComponent} consisting of another model. A <code>SubmodelComponent</code> can have so-called "interface pins" connecting the
31  * inner and outer models.
32  */
33 public abstract class SubmodelComponent extends GUIComponent
34 {
35         /**
36          * A modifiable view of {@link #submodel}.
37          */
38         protected final ViewModelModifiable submodelModifiable;
39         /**
40          * The model this {@link SubmodelComponent} consists of.
41          */
42         public final ViewModel submodel;
43         /**
44          * The list of all submodel interface pins of this {@link SubmodelComponent} on the submodel side.
45          */
46         private final Map<String, MovablePin> submodelPins;
47         /**
48          * An unmodifiable view of {@link #submodelPins}.
49          */
50         private final Map<String, MovablePin> submodelMovablePinsUnmodifiable;
51         /**
52          * An unmodifiable view of {@link #submodelPins} where pins are not movable.
53          */
54         private final Map<String, Pin> submodelUnmovablePinsUnmodifiable;
55         /**
56          * The list of all submodel interface pins of this {@link SubmodelComponent} on the supermodel side.
57          */
58         private final Map<String, MovablePin> supermodelPins;
59         /**
60          * An unmodifiable view of {@link #supermodelPins}.
61          */
62         private final Map<String, MovablePin> supermodelMovablePinsUnmodifiable;
63         /**
64          * An unmodifiable view of {@link #supermodelPins} where pins are not movable.
65          */
66         private final Map<String, Pin> supermodelUnmovablePinsUnmodifiable;
67         /**
68          * A pseudo-component containing all submodel interface pins on the submodel side.
69          */
70         private final SubmodelInterface submodelInterface;
71
72         /**
73          * The list of all high level state IDs this component supports without delegating to subcomponents.
74          */
75         private final Set<String> highLevelAtomicStates;
76         /**
77          * A map of high level state subcomponent IDs to the {@link GUIComponent} high level state access requests are delegated to.
78          */
79         private final Map<String, GUIComponent> subcomponentsByHighLevelStateSubcomponentID;
80
81         /**
82          * The factor by which the submodel is scaled when rendering.
83          */
84         private double submodelScale;
85         /**
86          * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel starts to
87          * be visible.
88          */
89         private double maxVisibleRegionFillRatioForAlpha0;
90         /**
91          * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel is fully
92          * visible.
93          */
94         private double minVisibleRegionFillRatioForAlpha1;
95         /**
96          * The renderer used for rendering the submodel.
97          */
98         private final LogicUIRenderer renderer;
99
100         // creation and destruction
101
102         public SubmodelComponent(ViewModelModifiable model)
103         {
104                 super(model);
105                 this.submodelModifiable = new ViewModelModifiable();
106                 this.submodel = submodelModifiable;
107                 this.submodelPins = new HashMap<>();
108                 this.submodelMovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
109                 this.submodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
110                 this.supermodelPins = new HashMap<>();
111                 this.supermodelMovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
112                 this.supermodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
113                 this.submodelInterface = new SubmodelInterface(submodelModifiable);
114
115                 this.highLevelAtomicStates = new HashSet<>();
116                 this.subcomponentsByHighLevelStateSubcomponentID = new HashMap<>();
117
118                 this.submodelScale = 1;
119                 this.maxVisibleRegionFillRatioForAlpha0 = 0.4;
120                 this.minVisibleRegionFillRatioForAlpha1 = 0.8;
121                 this.renderer = new LogicUIRenderer(submodelModifiable);
122
123                 submodelModifiable.addRedrawListener(this::requestRedraw);
124         }
125
126         // pins
127
128         /**
129          * Adds a new submodel interface pin.
130          * 
131          * @param supermodelPin the submodel interface pin on the supermodel side
132          * 
133          * @return the submodel interface pin on the submodel side
134          * 
135          * @author Daniel Kirschten
136          */
137         protected Pin addSubmodelInterface(MovablePin supermodelPin)
138         {
139                 super.addPin(supermodelPin);// do this first to be fail-fast if the supermodel does not belong to this component
140
141                 String name = supermodelPin.name;
142                 MovablePin submodelPin = new MovablePin(submodelInterface, name, supermodelPin.logicWidth, supermodelPin.getRelX() / submodelScale,
143                                 supermodelPin.getRelY() / submodelScale);
144
145                 submodelPin.addPinMovedListener(p ->
146                 {
147                         double newRelX = p.getRelX() * submodelScale;
148                         double newRelY = p.getRelY() * submodelScale;
149                         if (supermodelPin.getRelX() != newRelX || supermodelPin.getRelY() != newRelY)
150                                 supermodelPin.setRelPos(newRelX, newRelY);
151                 });
152                 supermodelPin.addPinMovedListener(p ->
153                 {
154                         double newRelX = p.getRelX() / submodelScale;
155                         double newRelY = p.getRelY() / submodelScale;
156                         if (submodelPin.getRelX() != newRelX || submodelPin.getRelY() != newRelY)
157                                 submodelPin.setRelPos(newRelX, newRelY);
158                 });
159
160                 submodelInterface.addPin(submodelPin);
161
162                 submodelPins.put(name, submodelPin);
163                 supermodelPins.put(name, supermodelPin);
164
165                 // no need to call requestRedraw() because addPin() will request a redraw
166                 return submodelPin;
167         }
168
169         /**
170          * Removes a submodel interface pin.
171          * 
172          * @author Daniel Kirschten
173          */
174         protected void removeSubmodelInterface(String name)
175         {
176                 super.removePin(name);// do this first to be fail-fast if this component doesn't have a pin with the given name
177                 Pin submodelPin = submodelPins.remove(name);
178                 submodelInterface.removePin(submodelPin.name);
179                 supermodelPins.remove(name);
180
181                 // no need to call requestRedraw() because removePin() will request a redraw
182         }
183
184         /**
185          * Returns a collection of submodel interface pins on the submodel side of this component.
186          * 
187          * @author Daniel Kirschten
188          */
189         public Map<String, Pin> getSubmodelPins()
190         {
191                 return submodelUnmovablePinsUnmodifiable;
192         }
193
194         /**
195          * Returns the submodel interface pin with the given name on the submodel side of this component.
196          * 
197          * @author Daniel Kirschten
198          */
199         public Pin getSubmodelPin(String name)
200         {
201                 return getSubmodelMovablePin(name);
202         }
203
204         /**
205          * Returns a collection of movable submodel interface pins on the submodel side of this component.
206          * 
207          * @author Daniel Kirschten
208          */
209         protected Map<String, MovablePin> getSubmodelMovablePins()
210         {
211                 return submodelMovablePinsUnmodifiable;
212         }
213
214         /**
215          * Returns the movable submodel interface pin with the given name on the submodel side of this component.
216          * 
217          * @author Daniel Kirschten
218          */
219         protected MovablePin getSubmodelMovablePin(String name)
220         {
221                 return submodelPins.get(name);
222         }
223
224         /**
225          * Returns a collection of submodel interface pins on the supermodel side of this component.
226          * 
227          * @author Daniel Kirschten
228          */
229         public Map<String, Pin> getSupermodelPins()
230         {
231                 return supermodelUnmovablePinsUnmodifiable;
232         }
233
234         /**
235          * Returns the submodel interface pin with the given name on the supermodel side of this component.
236          * 
237          * @author Daniel Kirschten
238          */
239         public Pin getSupermodelPin(String name)
240         {
241                 return getSupermodelMovablePin(name);
242         }
243
244         /**
245          * Returns a collection of movable submodel interface pins on the supermodel side of this component.
246          * 
247          * @author Daniel Kirschten
248          */
249         protected Map<String, MovablePin> getSupermodelMovablePins()
250         {
251                 return supermodelMovablePinsUnmodifiable;
252         }
253
254         /**
255          * Returns the movable submodel interface pin with the given name on the supermodel side of this component.
256          * 
257          * @author Daniel Kirschten
258          */
259         protected MovablePin getSupermodelMovablePin(String name)
260         {
261                 return supermodelPins.get(name);
262         }
263
264         // high-level access
265
266         /**
267          * Adds the given subcomponent ID to the set of allowed subcomponent IDs and links the given {@link GUIComponent} as the delegate target
268          * for this subcomponent ID. <br>
269          * Note that this method does not affect whether {@link #setSubcomponentHighLevelState(String, String, Object)
270          * set}/{@link #getSubcomponentHighLevelState(String, String)} will be called. <br>
271          * See {@link GUIComponent#setHighLevelState(String, Object)} for details about subcomponent IDs.
272          * 
273          * @author Daniel Kirschten
274          */
275         protected void addHighLevelStateSubcomponentID(String subcomponentID, GUIComponent subcomponent)
276         {
277                 checkHighLevelStateIDPart(subcomponentID);
278                 subcomponentsByHighLevelStateSubcomponentID.put(subcomponentID, subcomponent);
279         }
280
281         /**
282          * Removes the given subcomponent ID from the set of allowed subcomponent IDs. <br>
283          * Note that this method does not affect whether {@link #setSubcomponentHighLevelState(String, String, Object)
284          * set}/{@link #getSubcomponentHighLevelState(String, String)} will be called.<br>
285          * See {@link GUIComponent#setHighLevelState(String, Object)} for details about subcomponent IDs.
286          * 
287          * @author Daniel Kirschten
288          */
289         protected void removeHighLevelStateSubcomponentID(String subcomponentID)
290         {
291                 subcomponentsByHighLevelStateSubcomponentID.remove(subcomponentID);
292         }
293
294         /**
295          * Adds the given atomic state ID to the set of allowed atomic state IDs. <br>
296          * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
297          * 
298          * @author Daniel Kirschten
299          */
300         protected void addAtomicHighLevelStateID(String stateID)
301         {
302                 checkHighLevelStateIDPart(stateID);
303                 highLevelAtomicStates.add(stateID);
304         }
305
306         /**
307          * Removes the given atomic state ID from the set of allowed atomic state IDs. <br>
308          * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
309          * 
310          * @author Daniel Kirschten
311          */
312         protected void removeAtomicHighLevelStateID(String stateID)
313         {
314                 highLevelAtomicStates.remove(stateID);
315         }
316
317         @Override
318         public final void setHighLevelState(String stateID, Object newState)
319         {
320                 int indexOfDot = stateID.indexOf('.');
321                 if (indexOfDot == -1)
322                         if (highLevelAtomicStates.contains(stateID))
323                                 setAtomicHighLevelState(stateID, newState);
324                         else
325                                 super.setHighLevelState(stateID, newState);
326                 else
327                         setSubcomponentHighLevelState(stateID.substring(0, indexOfDot), stateID.substring(indexOfDot + 1), newState);
328         }
329
330         /**
331          * This method is called in {@link #setHighLevelState(String, Object)} when the state ID is not atomic. The default implementation uses
332          * the information given to {@link #addHighLevelStateSubcomponentID(String, GUIComponent)
333          * add}/{@link #removeHighLevelStateSubcomponentID(String)} to decide which subcomponent to delegate to.<br>
334          * Note that {@link #addHighLevelStateSubcomponentID(String, GUIComponent) add}/{@link #removeHighLevelStateSubcomponentID(String)}
335          * don't affect whether this method will be called.
336          * 
337          * @author Daniel Kirschten
338          */
339         protected void setSubcomponentHighLevelState(String subcomponentID, String subcomponentHighLevelStateID, Object newState)
340         {
341                 GUIComponent subcomponent = subcomponentsByHighLevelStateSubcomponentID.get(subcomponentID);
342                 if (subcomponent != null)
343                         subcomponent.setHighLevelState(subcomponentHighLevelStateID, newState);
344                 else
345                         super.setHighLevelState(subcomponentID + "." + subcomponentHighLevelStateID, newState);
346         }
347
348         /**
349          * This method is called in {@link #setHighLevelState(String, Object)} when the state ID is atomic and in the set of allowed atomic
350          * state IDs. <br>
351          * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
352          * 
353          * @author Daniel Kirschten
354          */
355         @SuppressWarnings({ "static-method", "unused" }) // this method is intended to be overridden
356         protected void setAtomicHighLevelState(String stateID, Object newState)
357         {
358                 throw new IllegalStateException("Unknown high level state ID: " + stateID);
359         }
360
361         @Override
362         public final Object getHighLevelState(String stateID)
363         {
364                 int indexOfDot = stateID.indexOf('.');
365                 if (indexOfDot == -1)
366                 {
367                         if (highLevelAtomicStates.contains(stateID))
368                                 return getAtomicHighLevelState(stateID);
369                         return super.getHighLevelState(stateID);
370                 }
371                 return getSubcomponentHighLevelState(stateID.substring(0, indexOfDot), stateID.substring(indexOfDot + 1));
372         }
373
374         /**
375          * This method is called in {@link #getHighLevelState(String, Object)} when the state ID is not atomic. The default implementation uses
376          * the information given to {@link #addHighLevelStateSubcomponentID(String, GUIComponent)
377          * add}/{@link #removeHighLevelStateSubcomponentID(String)} to decide which subcomponent to delegate to. <br>
378          * Note that {@link #addHighLevelStateSubcomponentID(String, GUIComponent) add}/{@link #removeHighLevelStateSubcomponentID(String)}
379          * don't affect whether this method will be called.
380          * 
381          * @author Daniel Kirschten
382          */
383         protected Object getSubcomponentHighLevelState(String subcomponentID, String subcomponentHighLevelStateID)
384         {
385                 GUIComponent subcomponent = subcomponentsByHighLevelStateSubcomponentID.get(subcomponentID);
386                 if (subcomponent != null)
387                         return subcomponent.getHighLevelState(subcomponentHighLevelStateID);
388                 return super.getHighLevelState(subcomponentID + "." + subcomponentHighLevelStateID);
389         }
390
391         /**
392          * This method is called in {@link SubmodelComponent#getHighLevelState(String)} when the state ID is in the set of allowed atomic state
393          * IDs. <br>
394          * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
395          * 
396          * @author Daniel Kirschten
397          */
398         @SuppressWarnings("static-method") // this method is intended to be overridden
399         protected Object getAtomicHighLevelState(String stateID)
400         {
401                 throw new IllegalStateException("Unknown high level state ID: " + stateID);
402         }
403
404         private static void checkHighLevelStateIDPart(String stateIDPart)
405         {
406                 if (stateIDPart.indexOf('.') != -1)
407                         throw new IllegalArgumentException("Illegal high level state ID part (contains dot): " + stateIDPart);
408
409         }
410
411         // "graphical" operations
412
413         /**
414          * Sets the factor by which the submodel is scaled when rendering and calls redrawListeners. Note that the submodel interface pins will
415          * stay at their position relative to the supermodel, which means they will move relative to the submodel.
416          * 
417          * @author Daniel Kirschten
418          */
419         protected void setSubmodelScale(double submodelScale)
420         {
421                 this.submodelScale = submodelScale;
422
423                 for (Entry<String, MovablePin> e : supermodelPins.entrySet())
424                         getSubmodelMovablePin(e.getKey()).setRelPos(e.getValue().getRelX() * submodelScale, e.getValue().getRelY() * submodelScale);
425
426                 requestRedraw();// needed if there is no submodel interface pin
427         }
428
429         /**
430          * Returns the current factor by which the submodel is scaled when rendering.
431          * 
432          * @author Daniel Kirschten
433          */
434         protected double getSubmodelScale()
435         {
436                 return submodelScale;
437         }
438
439         @Override
440         public void render(GeneralGC gc, Rectangle visibleRegion)
441         {
442                 GCConfig conf = new GCConfig(gc);
443                 TranslatedGC tgc = new TranslatedGC(gc, getPosX(), getPosY(), submodelScale, true);
444                 conf.reset(tgc);
445                 double visibleRegionFillRatio = Math.max(getWidth() / visibleRegion.width, getHeight() / visibleRegion.height);
446                 double alphaFactor = map(visibleRegionFillRatio, maxVisibleRegionFillRatioForAlpha0, minVisibleRegionFillRatioForAlpha1, 0, 1);
447                 alphaFactor = Math.max(0, Math.min(1, alphaFactor));
448                 // we need to take the old alpha into account to support nested submodules better.
449                 int oldAlpha = gc.getAlpha();
450                 int submodelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * alphaFactor)));
451                 int labelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * (1 - alphaFactor))));
452                 if (submodelAlpha != 0)
453                 {
454                         gc.setAlpha(submodelAlpha);
455                         renderer.render(tgc, visibleRegion.translate(getPosX() / submodelScale, getPosY() / submodelScale, 1 / submodelScale));
456                 }
457                 if (labelAlpha != 0)
458                 {
459                         gc.setAlpha(labelAlpha);
460                         renderSymbol(gc, visibleRegion);
461                 }
462                 conf.reset(gc);
463                 // draw the outline after all other operations to make interface pins look better
464                 renderOutline(gc, visibleRegion);
465         }
466
467         /**
468          * Render the outline of this {@link SubmodelComponent}, e.g. the graphical elements that should stay visible if the submodel is drawn.
469          * 
470          * @author Daniel Kirschten
471          */
472         protected abstract void renderOutline(GeneralGC gc, Rectangle visibleRegion);
473
474         /**
475          * Render the symbol of this {@link SubmodelComponent}, e.g. the things that should be hidden if the submodel is drawn.
476          * 
477          * @author Daniel Kirschten
478          */
479         protected abstract void renderSymbol(GeneralGC gc, Rectangle visibleRegion);
480
481         private static double map(double val, double valMin, double valMax, double mapMin, double mapMax)
482         {
483                 return mapMin + (val - valMin) * (mapMax - mapMin) / (valMax - valMin);
484         }
485
486         @Override
487         public boolean clicked(double x, double y)
488         {
489                 double scaledX = (x - getPosX()) / submodelScale;
490                 double scaledY = (y - getPosY()) / submodelScale;
491                 for (GUIComponent component : submodel.getComponents())
492                         if (component.getBounds().contains(scaledX, scaledY) && component.clicked(scaledX, scaledY))
493                                 return true;
494                 return false;
495         }
496
497         // serializing
498
499         /**
500          * @return {@link SubmodelComponentParams}, which describe this {@link SubmodelComponent}.
501          */
502         public SubmodelComponentParams calculateParams()
503         {
504                 SubmodelComponentParams params = new SubmodelComponentParams();
505                 params.name = getIdentifier();
506                 params.type = SubmodelComponent.class.getSimpleName();
507                 params.composition = calculateCompositionParams();
508
509                 params.width = getWidth();
510                 params.height = getHeight();
511
512                 InterfacePinParams[] iPins = new InterfacePinParams[getPins().size()];
513                 int i = 0;
514                 for (Pin p : getPins().values())
515                 {
516                         InterfacePinParams iPinParams = new InterfacePinParams();
517                         iPins[i] = iPinParams;
518                         iPinParams.location = p.getRelPos();
519                         iPinParams.name = p.name;
520                         iPinParams.logicWidth = p.logicWidth;
521                         i++;
522                 }
523                 params.interfacePins = iPins;
524                 return params;
525         }
526
527         protected ComponentCompositionParams calculateCompositionParams()
528         {
529                 ComponentCompositionParams params = new ComponentCompositionParams();
530                 params.innerScale = getSubmodelScale();
531
532                 List<GUIComponent> compList = submodelModifiable.getComponents();
533                 Iterator<GUIComponent> componentIt = compList.iterator();
534                 componentIt.next(); // Skip inner SubmodelInterface
535                 InnerComponentParams[] comps = new InnerComponentParams[compList.size() - 1];
536                 int i = 0;
537                 while (componentIt.hasNext())
538                 {
539                         GUIComponent component = componentIt.next();
540                         InnerComponentParams inner = new InnerComponentParams();
541                         comps[i] = inner;
542                         inner.params = component.getInstantiationParameters();
543                         inner.pos = new Point(component.getPosX(), component.getPosY());
544                         inner.name = component.getIdentifier();
545                         i++;
546                 }
547                 params.subComps = comps;
548
549                 List<GUIWire> wireList = submodelModifiable.getWires();
550                 InnerWireParams wires[] = new InnerWireParams[wireList.size()];
551                 i = 0;
552                 for (GUIWire wire : wireList)
553                 {
554                         InnerWireParams inner = new InnerWireParams();
555                         wires[i] = inner;
556                         InnerPinParams pin1Params = new InnerPinParams(), pin2Params = new InnerPinParams();
557
558                         pin1Params.pinName = wire.getPin1().name;
559                         pin1Params.compId = compList.indexOf(wire.getPin1().component);
560                         pin2Params.pinName = wire.getPin2().name;
561                         pin2Params.compId = compList.indexOf(wire.getPin2().component);
562                         inner.pin1 = pin1Params;
563                         inner.pin2 = pin2Params;
564                         inner.path = wire.getPath();
565                         i++;
566                 }
567                 params.innerWires = wires;
568                 return params;
569         }
570
571         // operations no longer supported
572
573         @Override
574         protected void addPin(Pin pin)
575         {
576                 throw new UnsupportedOperationException("Can't add pins to a SubmodelComponent directly, call addSubmodelInterface instead");
577         }
578
579         @Override
580         protected void removePin(String name)
581         {
582                 throw new UnsupportedOperationException("Can't remove pins of a SubmodelComponent directly, call removeSubmodelInterface instead");
583         }
584 }