Cleaned up initializing of ModelComponents (also improved HLSDebugShell)
[Mograsim.git] / net.mograsim.logic.model / src / net / mograsim / logic / model / model / components / ModelComponent.java
1 package net.mograsim.logic.model.model.components;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.function.Consumer;
9
10 import net.haspamelodica.swt.helper.gcs.GeneralGC;
11 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
12 import net.mograsim.logic.model.model.LogicModelModifiable;
13 import net.mograsim.logic.model.model.wires.Pin;
14 import net.mograsim.logic.model.serializing.IdentifyParams;
15 import net.mograsim.logic.model.serializing.JSONSerializable;
16 import net.mograsim.logic.model.snippets.HighLevelStateHandler;
17
18 /**
19  * The base class for all model components.<br>
20  * A <code>ModelComponent</code> has a reference to the LogicModel it belongs to.<br>
21  * A <code>ModelComponent</code> has a name. This name is unique in the model the <code>ModelComponent</code> belongs to.<br>
22  * A <code>ModelComponent</code> has a position and size. The size can only be modified by subclasses.
23  * 
24  * @author Daniel Kirschten
25  */
26 public abstract class ModelComponent implements JSONSerializable
27 {
28         /**
29          * The model this component is a part of.
30          */
31         protected final LogicModelModifiable model;
32         /**
33          * The name of this component. Is unique for all components in its model.
34          */
35         public final String name;
36         private final Rectangle bounds;
37         /**
38          * The list of all pins of this component by name.
39          */
40         private final Map<String, Pin> pinsByName;
41         /**
42          * An unmodifiable view of {@link #pinsByName}.
43          */
44         protected final Map<String, Pin> pinsUnmodifiable;
45
46         private final List<Consumer<? super ModelComponent>> componentMovedListeners;
47         private final List<Consumer<? super ModelComponent>> componentResizedListeners;
48         private final List<Consumer<? super Pin>> pinAddedListeners;
49         private final List<Consumer<? super Pin>> pinRemovedListeners;
50
51         private HighLevelStateHandler highLevelStateHandler;
52
53         // creation and destruction
54
55         public ModelComponent(LogicModelModifiable model, String name)
56         {
57                 this(model, name, true);
58         }
59
60         /**
61          * Creates a new {@link ModelComponent} and, if <code>callInit</code>, initializes the component (See {@link #init()}).<br>
62          * If <code>callInit==false</code>, make sure to call {@link #init()}!
63          * 
64          * @author Daniel Kirschten
65          */
66         protected ModelComponent(LogicModelModifiable model, String name, boolean callInit)
67         {
68                 this.model = model;
69                 this.name = name == null ? model.getDefaultComponentName(this) : name;
70                 this.bounds = new Rectangle(0, 0, 0, 0);
71                 this.pinsByName = new HashMap<>();
72                 this.pinsUnmodifiable = Collections.unmodifiableMap(pinsByName);
73
74                 this.componentMovedListeners = new ArrayList<>();
75                 this.componentResizedListeners = new ArrayList<>();
76                 this.pinAddedListeners = new ArrayList<>();
77                 this.pinRemovedListeners = new ArrayList<>();
78
79                 if (callInit)
80                         init();
81         }
82
83         /**
84          * Initializes this component. This method should be called exactly once in this component's constructor.<br>
85          * Currently, this method only registers this component in the model.
86          */
87         protected void init()
88         {
89                 model.componentCreated(this, this::destroyed);
90         }
91
92         /**
93          * Destroys this component. This method is called from {@link LogicModelModifiable#componentDestroyed(ModelComponent)
94          * destroyComponent()} of the model this component is a part of.<br>
95          * When overriding, make sure to also call the original implementation.
96          * 
97          * @author Daniel Kirschten
98          */
99         protected void destroyed()
100         {
101                 pinsByName.values().forEach(this::removePinWithoutRedraw);
102         }
103
104         // pins
105
106         /**
107          * Adds the given pin to this component and calls pinAddedListeners and redrawListeners.
108          * 
109          * @throws IllegalArgumentException if the pin doesn't belong to this component
110          * @throws IllegalArgumentException if there already is a pin with the given name
111          * 
112          * @author Daniel Kirschten
113          */
114         protected void addPin(Pin pin)
115         {
116                 if (pin.component != this)
117                         throw new IllegalArgumentException("Can't add a pin not belonging to this component!");
118                 if (pinsByName.containsKey(pin.name))
119                         throw new IllegalArgumentException("Duplicate pin name: " + pin.name);
120                 pinsByName.put(pin.name, pin);
121                 callPinAddedListeners(pin);
122                 model.requestRedraw();
123         }
124
125         /**
126          * Removes the given pin from this component and calls pinAddedListeners and redrawListeners.
127          * 
128          * @throws NullPointerException if there was no pin with this name
129          * 
130          * @author Daniel Kirschten
131          */
132         protected void removePin(String name)
133         {
134                 removePinWithoutRedraw(pinsByName.remove(name));
135                 model.requestRedraw();
136         }
137
138         private void removePinWithoutRedraw(Pin pin)
139         {
140                 pin.destroyed();
141                 callPinRemovedListeners(pin);
142         }
143
144         /**
145          * Returns a collection of pins of this component.
146          * 
147          * @author Daniel Kirschten
148          */
149         public Map<String, Pin> getPins()
150         {
151                 return pinsUnmodifiable;
152         }
153
154         /**
155          * Returns the pin with the given name of this component.
156          * 
157          * @throws IllegalArgumentException if there is no pin with the given name
158          * 
159          * @author Daniel Kirschten
160          */
161         public Pin getPin(String name)
162         {
163                 Pin pin = pinsByName.get(name);
164                 if (pin == null)
165                         throw new IllegalArgumentException("No pin with the name " + name);
166                 return pin;
167         }
168
169         // high-level access
170
171         /**
172          * @author Daniel Kirschten
173          */
174         protected void setHighLevelStateHandler(HighLevelStateHandler highLevelStateHandler)
175         {
176                 this.highLevelStateHandler = highLevelStateHandler;
177         }
178
179         public HighLevelStateHandler getHighLevelStateHandler()
180         {
181                 return highLevelStateHandler;
182         }
183
184         /**
185          * Gets the current value of the given high-level state. <br>
186          * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
187          * 
188          * @see #setHighLevelState(String, Object)
189          * @see HighLevelStateHandler#getHighLevelState(String)
190          * 
191          * @author Daniel Kirschten
192          */
193         public Object getHighLevelState(String stateID)
194         {
195                 return highLevelStateHandler.getHighLevelState(stateID);
196         }
197
198         /**
199          * Sets the given high-level state to the given value. <br>
200          * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
201          * 
202          * @see #getHighLevelState(String)
203          * @see HighLevelStateHandler#setHighLevelState(String, Object)
204          * 
205          * @author Daniel Kirschten
206          */
207         public void setHighLevelState(String stateID, Object newState)
208         {
209                 highLevelStateHandler.setHighLevelState(stateID, newState);
210         }
211
212         // "graphical" operations
213
214         /**
215          * Sets the position of this component and calls componentMovedListeners and redrawListeners.
216          * 
217          * @author Daniel Kirschten
218          */
219         public void moveTo(double x, double y)
220         {
221                 bounds.x = x;
222                 bounds.y = y;
223                 callComponentMovedListeners();
224                 model.requestRedraw();
225         }
226
227         /**
228          * Sets the size of this component and calls redrawListeners.
229          * 
230          * @author Daniel Kirschten
231          */
232         protected void setSize(double width, double height)
233         {
234                 bounds.width = width;
235                 bounds.height = height;
236                 callComponentResizedListener();
237                 model.requestRedraw();
238         }
239
240         /**
241          * Returns the bounds of this component. Is a bit slower than {@link #getPosX()}, {@link #getPosY()}, {@link #getWidth},
242          * {@link #getHeight}, because new objects are created.
243          * 
244          * @author Daniel Kirschten
245          */
246         public final Rectangle getBounds()
247         {
248                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
249         }
250
251         /**
252          * Returns the x coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
253          * 
254          * @author Daniel Kirschten
255          */
256         public double getPosX()
257         {
258                 return bounds.x;
259         }
260
261         /**
262          * Returns the y coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
263          * 
264          * @author Daniel Kirschten
265          */
266         public double getPosY()
267         {
268                 return bounds.y;
269         }
270
271         /**
272          * Returns the (graphical) width of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
273          * 
274          * @author Daniel Kirschten
275          */
276         public double getWidth()
277         {
278                 return bounds.width;
279         }
280
281         /**
282          * Returns the height of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
283          * 
284          * @author Daniel Kirschten
285          */
286         public double getHeight()
287         {
288                 return bounds.height;
289         }
290
291         /**
292          * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
293          * click.
294          * 
295          * @author Daniel Kirschten
296          */
297         @SuppressWarnings({ "static-method", "unused" }) // this method is inteded to be overridden
298         public boolean clicked(double x, double y)
299         {
300                 return false;
301         }
302
303         /**
304          * Render this component to the given gc, in absoulute coordinates.
305          * 
306          * @author Daniel Kirschten
307          */
308         public abstract void render(GeneralGC gc, Rectangle visibleRegion);
309
310         // serializing
311
312         @Override
313         public Object getParamsForSerializing(IdentifyParams idParams)
314         {
315                 return null;
316         }
317
318         // listeners
319
320         // @formatter:off
321         public void addComponentMovedListener      (Consumer<? super ModelComponent> listener) {componentMovedListeners  .add   (listener);}
322         public void addComponentResizedListener    (Consumer<? super ModelComponent> listener) {componentResizedListeners.add   (listener);}
323         public void addPinAddedListener            (Consumer<? super Pin         > listener) {pinAddedListeners        .add   (listener);}
324         public void addPinRemovedListener          (Consumer<? super Pin         > listener) {pinRemovedListeners      .add   (listener);}
325
326         public void removeComponentMovedListener   (Consumer<? super ModelComponent> listener) {componentMovedListeners  .remove(listener);}
327         public void removeComponentResizedListener (Consumer<? super ModelComponent> listener) {componentResizedListeners.remove(listener);}
328         public void removePinAddedListener         (Consumer<? super Pin         > listener) {pinAddedListeners        .remove(listener);}
329         public void removePinRemovedListener       (Consumer<? super Pin         > listener) {pinRemovedListeners      .remove(listener);}
330
331         private void callComponentMovedListeners (     ) {componentMovedListeners  .forEach(l -> l.accept(this));}
332         private void callComponentResizedListener(     ) {componentResizedListeners.forEach(l -> l.accept(this));}
333         private void callPinAddedListeners       (Pin p) {pinAddedListeners        .forEach(l -> l.accept(p   ));}
334         private void callPinRemovedListeners     (Pin p) {pinRemovedListeners      .forEach(l -> l.accept(p   ));}
335         // @formatter:on
336 }