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