Renamed ViewModel to LogicModel
[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(p -> pinRemovedListeners.forEach(l -> l.accept(p)));
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                 Pin pin = pinsByName.remove(name);
116                 callPinRemovedListeners(pin);
117                 model.requestRedraw();
118         }
119
120         /**
121          * Returns a collection of pins of this component.
122          * 
123          * @author Daniel Kirschten
124          */
125         public Map<String, Pin> getPins()
126         {
127                 return pinsUnmodifiable;
128         }
129
130         /**
131          * Returns the pin with the given name of this component.
132          * 
133          * @throws IllegalArgumentException if there is no pin with the given name
134          * 
135          * @author Daniel Kirschten
136          */
137         public Pin getPin(String name)
138         {
139                 Pin pin = pinsByName.get(name);
140                 if (pin == null)
141                         throw new IllegalArgumentException("No pin with the name " + name);
142                 return pin;
143         }
144
145         // high-level access
146
147         /**
148          * @author Daniel Kirschten
149          */
150         protected void setHighLevelStateHandler(HighLevelStateHandler highLevelStateHandler)
151         {
152                 this.highLevelStateHandler = highLevelStateHandler;
153         }
154
155         public HighLevelStateHandler getHighLevelStateHandler()
156         {
157                 return highLevelStateHandler;
158         }
159
160         /**
161          * Gets the current value of the given high-level state. <br>
162          * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
163          * 
164          * @see #setHighLevelState(String, Object)
165          * @see HighLevelStateHandler#getHighLevelState(String)
166          * 
167          * @author Daniel Kirschten
168          */
169         public Object getHighLevelState(String stateID)
170         {
171                 return highLevelStateHandler.getHighLevelState(stateID);
172         }
173
174         /**
175          * Sets the given high-level state to the given value. <br>
176          * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
177          * 
178          * @see #getHighLevelState(String)
179          * @see HighLevelStateHandler#setHighLevelState(String, Object)
180          * 
181          * @author Daniel Kirschten
182          */
183         public void setHighLevelState(String stateID, Object newState)
184         {
185                 highLevelStateHandler.setHighLevelState(stateID, newState);
186         }
187
188         // "graphical" operations
189
190         /**
191          * Sets the position of this component and calls componentMovedListeners and redrawListeners.
192          * 
193          * @author Daniel Kirschten
194          */
195         public void moveTo(double x, double y)
196         {
197                 bounds.x = x;
198                 bounds.y = y;
199                 callComponentMovedListeners();
200                 model.requestRedraw();
201         }
202
203         /**
204          * Sets the size of this component and calls redrawListeners.
205          * 
206          * @author Daniel Kirschten
207          */
208         protected void setSize(double width, double height)
209         {
210                 bounds.width = width;
211                 bounds.height = height;
212                 callComponentResizedListener();
213                 model.requestRedraw();
214         }
215
216         /**
217          * Returns the bounds of this component. Is a bit slower than {@link #getPosX()}, {@link #getPosY()}, {@link #getWidth},
218          * {@link #getHeight}, because new objects are created.
219          * 
220          * @author Daniel Kirschten
221          */
222         public final Rectangle getBounds()
223         {
224                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
225         }
226
227         /**
228          * Returns the x coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
229          * 
230          * @author Daniel Kirschten
231          */
232         public double getPosX()
233         {
234                 return bounds.x;
235         }
236
237         /**
238          * Returns the y coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
239          * 
240          * @author Daniel Kirschten
241          */
242         public double getPosY()
243         {
244                 return bounds.y;
245         }
246
247         /**
248          * Returns the (graphical) width of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
249          * 
250          * @author Daniel Kirschten
251          */
252         public double getWidth()
253         {
254                 return bounds.width;
255         }
256
257         /**
258          * Returns the height of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
259          * 
260          * @author Daniel Kirschten
261          */
262         public double getHeight()
263         {
264                 return bounds.height;
265         }
266
267         /**
268          * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
269          * click.
270          * 
271          * @author Daniel Kirschten
272          */
273         @SuppressWarnings({ "static-method", "unused" }) // this method is inteded to be overridden
274         public boolean clicked(double x, double y)
275         {
276                 return false;
277         }
278
279         /**
280          * Render this component to the given gc, in absoulute coordinates.
281          * 
282          * @author Daniel Kirschten
283          */
284         public abstract void render(GeneralGC gc, Rectangle visibleRegion);
285
286         // serializing
287
288         @Override
289         public Object getParamsForSerializing(IdentifyParams idParams)
290         {
291                 return null;
292         }
293
294         // listeners
295
296         // @formatter:off
297         public void addComponentMovedListener      (Consumer<? super ModelComponent> listener) {componentMovedListeners  .add   (listener);}
298         public void addComponentResizedListener    (Consumer<? super ModelComponent> listener) {componentResizedListeners.add   (listener);}
299         public void addPinAddedListener            (Consumer<? super Pin         > listener) {pinAddedListeners        .add   (listener);}
300         public void addPinRemovedListener          (Consumer<? super Pin         > listener) {pinRemovedListeners      .add   (listener);}
301
302         public void removeComponentMovedListener   (Consumer<? super ModelComponent> listener) {componentMovedListeners  .remove(listener);}
303         public void removeComponentResizedListener (Consumer<? super ModelComponent> listener) {componentResizedListeners.remove(listener);}
304         public void removePinAddedListener         (Consumer<? super Pin         > listener) {pinAddedListeners        .remove(listener);}
305         public void removePinRemovedListener       (Consumer<? super Pin         > listener) {pinRemovedListeners      .remove(listener);}
306
307         private void callComponentMovedListeners (     ) {componentMovedListeners  .forEach(l -> l.accept(this));}
308         private void callComponentResizedListener(     ) {componentResizedListeners.forEach(l -> l.accept(this));}
309         private void callPinAddedListeners       (Pin p) {pinAddedListeners        .forEach(l -> l.accept(p   ));}
310         private void callPinRemovedListeners     (Pin p) {pinRemovedListeners      .forEach(l -> l.accept(p   ));}
311         // @formatter:on
312 }