Wires connected to a component now get deleted with the component
[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 = model;
58                 this.name = name == null ? model.getDefaultComponentName(this) : name;
59                 this.bounds = new Rectangle(0, 0, 0, 0);
60                 this.pinsByName = new HashMap<>();
61                 this.pinsUnmodifiable = Collections.unmodifiableMap(pinsByName);
62
63                 this.componentMovedListeners = new ArrayList<>();
64                 this.componentResizedListeners = new ArrayList<>();
65                 this.pinAddedListeners = new ArrayList<>();
66                 this.pinRemovedListeners = new ArrayList<>();
67
68                 // TODO this will crash the high level state debug shell because submodel is not yet set.
69                 // The same problem exists in LogicModelModifiable.getDefaultComponentName; see there
70                 model.componentCreated(this, this::destroyed);
71         }
72
73         /**
74          * Destroys this component. This method is called from {@link LogicModelModifiable#componentDestroyed(ModelComponent)
75          * destroyComponent()} of the model this component is a part of.<br>
76          * When overriding, make sure to also call the original implementation.
77          * 
78          * @author Daniel Kirschten
79          */
80         protected void destroyed()
81         {
82                 pinsByName.values().forEach(this::removePinWithoutRedraw);
83         }
84
85         // pins
86
87         /**
88          * Adds the given pin to this component and calls pinAddedListeners and redrawListeners.
89          * 
90          * @throws IllegalArgumentException if the pin doesn't belong to this component
91          * @throws IllegalArgumentException if there already is a pin with the given name
92          * 
93          * @author Daniel Kirschten
94          */
95         protected void addPin(Pin pin)
96         {
97                 if (pin.component != this)
98                         throw new IllegalArgumentException("Can't add a pin not belonging to this component!");
99                 if (pinsByName.containsKey(pin.name))
100                         throw new IllegalArgumentException("Duplicate pin name: " + pin.name);
101                 pinsByName.put(pin.name, pin);
102                 callPinAddedListeners(pin);
103                 model.requestRedraw();
104         }
105
106         /**
107          * Removes the given pin from this component and calls pinAddedListeners and redrawListeners.
108          * 
109          * @throws NullPointerException if there was no pin with this name
110          * 
111          * @author Daniel Kirschten
112          */
113         protected void removePin(String name)
114         {
115                 removePinWithoutRedraw(pinsByName.remove(name));
116                 model.requestRedraw();
117         }
118
119         private void removePinWithoutRedraw(Pin pin)
120         {
121                 pin.destroyed();
122                 callPinRemovedListeners(pin);
123         }
124
125         /**
126          * Returns a collection of pins of this component.
127          * 
128          * @author Daniel Kirschten
129          */
130         public Map<String, Pin> getPins()
131         {
132                 return pinsUnmodifiable;
133         }
134
135         /**
136          * Returns the pin with the given name of this component.
137          * 
138          * @throws IllegalArgumentException if there is no pin with the given name
139          * 
140          * @author Daniel Kirschten
141          */
142         public Pin getPin(String name)
143         {
144                 Pin pin = pinsByName.get(name);
145                 if (pin == null)
146                         throw new IllegalArgumentException("No pin with the name " + name);
147                 return pin;
148         }
149
150         // high-level access
151
152         /**
153          * @author Daniel Kirschten
154          */
155         protected void setHighLevelStateHandler(HighLevelStateHandler highLevelStateHandler)
156         {
157                 this.highLevelStateHandler = highLevelStateHandler;
158         }
159
160         public HighLevelStateHandler getHighLevelStateHandler()
161         {
162                 return highLevelStateHandler;
163         }
164
165         /**
166          * Gets the current value of the given high-level state. <br>
167          * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
168          * 
169          * @see #setHighLevelState(String, Object)
170          * @see HighLevelStateHandler#getHighLevelState(String)
171          * 
172          * @author Daniel Kirschten
173          */
174         public Object getHighLevelState(String stateID)
175         {
176                 return highLevelStateHandler.getHighLevelState(stateID);
177         }
178
179         /**
180          * Sets the given high-level state to the given value. <br>
181          * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
182          * 
183          * @see #getHighLevelState(String)
184          * @see HighLevelStateHandler#setHighLevelState(String, Object)
185          * 
186          * @author Daniel Kirschten
187          */
188         public void setHighLevelState(String stateID, Object newState)
189         {
190                 highLevelStateHandler.setHighLevelState(stateID, newState);
191         }
192
193         // "graphical" operations
194
195         /**
196          * Sets the position of this component and calls componentMovedListeners and redrawListeners.
197          * 
198          * @author Daniel Kirschten
199          */
200         public void moveTo(double x, double y)
201         {
202                 bounds.x = x;
203                 bounds.y = y;
204                 callComponentMovedListeners();
205                 model.requestRedraw();
206         }
207
208         /**
209          * Sets the size of this component and calls redrawListeners.
210          * 
211          * @author Daniel Kirschten
212          */
213         protected void setSize(double width, double height)
214         {
215                 bounds.width = width;
216                 bounds.height = height;
217                 callComponentResizedListener();
218                 model.requestRedraw();
219         }
220
221         /**
222          * Returns the bounds of this component. Is a bit slower than {@link #getPosX()}, {@link #getPosY()}, {@link #getWidth},
223          * {@link #getHeight}, because new objects are created.
224          * 
225          * @author Daniel Kirschten
226          */
227         public final Rectangle getBounds()
228         {
229                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
230         }
231
232         /**
233          * Returns the x coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
234          * 
235          * @author Daniel Kirschten
236          */
237         public double getPosX()
238         {
239                 return bounds.x;
240         }
241
242         /**
243          * Returns the y coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
244          * 
245          * @author Daniel Kirschten
246          */
247         public double getPosY()
248         {
249                 return bounds.y;
250         }
251
252         /**
253          * Returns the (graphical) width of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
254          * 
255          * @author Daniel Kirschten
256          */
257         public double getWidth()
258         {
259                 return bounds.width;
260         }
261
262         /**
263          * Returns the height of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
264          * 
265          * @author Daniel Kirschten
266          */
267         public double getHeight()
268         {
269                 return bounds.height;
270         }
271
272         /**
273          * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
274          * click.
275          * 
276          * @author Daniel Kirschten
277          */
278         @SuppressWarnings({ "static-method", "unused" }) // this method is inteded to be overridden
279         public boolean clicked(double x, double y)
280         {
281                 return false;
282         }
283
284         /**
285          * Render this component to the given gc, in absoulute coordinates.
286          * 
287          * @author Daniel Kirschten
288          */
289         public abstract void render(GeneralGC gc, Rectangle visibleRegion);
290
291         // serializing
292
293         @Override
294         public Object getParamsForSerializing(IdentifyParams idParams)
295         {
296                 return null;
297         }
298
299         // listeners
300
301         // @formatter:off
302         public void addComponentMovedListener      (Consumer<? super ModelComponent> listener) {componentMovedListeners  .add   (listener);}
303         public void addComponentResizedListener    (Consumer<? super ModelComponent> listener) {componentResizedListeners.add   (listener);}
304         public void addPinAddedListener            (Consumer<? super Pin         > listener) {pinAddedListeners        .add   (listener);}
305         public void addPinRemovedListener          (Consumer<? super Pin         > listener) {pinRemovedListeners      .add   (listener);}
306
307         public void removeComponentMovedListener   (Consumer<? super ModelComponent> listener) {componentMovedListeners  .remove(listener);}
308         public void removeComponentResizedListener (Consumer<? super ModelComponent> listener) {componentResizedListeners.remove(listener);}
309         public void removePinAddedListener         (Consumer<? super Pin         > listener) {pinAddedListeners        .remove(listener);}
310         public void removePinRemovedListener       (Consumer<? super Pin         > listener) {pinRemovedListeners      .remove(listener);}
311
312         private void callComponentMovedListeners (     ) {componentMovedListeners  .forEach(l -> l.accept(this));}
313         private void callComponentResizedListener(     ) {componentResizedListeners.forEach(l -> l.accept(this));}
314         private void callPinAddedListeners       (Pin p) {pinAddedListeners        .forEach(l -> l.accept(p   ));}
315         private void callPinRemovedListeners     (Pin p) {pinRemovedListeners      .forEach(l -> l.accept(p   ));}
316         // @formatter:on
317 }