Restructured serializing / deserializing
[Mograsim.git] / net.mograsim.logic.ui / src / net / mograsim / logic / ui / model / components / GUIComponent.java
1 package net.mograsim.logic.ui.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.ui.model.ViewModelModifiable;
13 import net.mograsim.logic.ui.model.wires.Pin;
14
15 /**
16  * The base class for all GUI components.<br>
17  * A <code>GUIComponent</code> has a position and size. The size can only be modified by subclasses.<br>
18  * 
19  * @author Daniel Kirschten
20  */
21 public abstract class GUIComponent
22 {
23         /**
24          * The model this component is a part of.
25          */
26         protected final ViewModelModifiable model;
27         private final Rectangle bounds;
28         /**
29          * The list of all pins of this component by name.
30          */
31         private final Map<String, Pin> pinsByName;
32         /**
33          * An unmodifiable view of {@link #pinsByName}.
34          */
35         protected final Map<String, Pin> pinsUnmodifiable;
36
37         private final List<Consumer<? super GUIComponent>> componentMovedListeners;
38         private final List<Consumer<? super Pin>> pinAddedListeners;
39         private final List<Consumer<? super Pin>> pinRemovedListeners;
40         private final List<Runnable> redrawListeners;
41
42         private final Runnable redrawListenerForSubcomponents;
43
44         // creation and destruction
45
46         public GUIComponent(ViewModelModifiable model)
47         {
48                 this.model = model;
49                 this.bounds = new Rectangle(0, 0, 0, 0);
50                 this.pinsByName = new HashMap<>();
51                 this.pinsUnmodifiable = Collections.unmodifiableMap(pinsByName);
52
53                 this.componentMovedListeners = new ArrayList<>();
54                 this.pinAddedListeners = new ArrayList<>();
55                 this.pinRemovedListeners = new ArrayList<>();
56                 this.redrawListeners = new ArrayList<>();
57
58                 redrawListenerForSubcomponents = this::requestRedraw;
59
60                 model.componentCreated(this);
61         }
62
63         /**
64          * Destroys this component. This method implicitly calls {@link ViewModelModifiable#componentDestroyed(GUIComponent)
65          * componentDestroyed()} for the model this component is a part of.
66          * 
67          * @author Daniel Kirschten
68          */
69         public void destroy()
70         {
71                 pinsByName.values().forEach(p -> pinRemovedListeners.forEach(l -> l.accept(p)));
72                 model.componentDestroyed(this);
73         }
74
75         // pins
76
77         /**
78          * Adds the given pin to this component and calls pinAddedListeners and redrawListeners.
79          * 
80          * @throws IllegalArgumentException if the pin doesn't belong to this component
81          * @throws IllegalArgumentException if there already is a pin with the given name
82          * 
83          * @author Daniel Kirschten
84          */
85         protected void addPin(Pin pin)
86         {
87                 if (pin.component != this)
88                         throw new IllegalArgumentException("Can't add a pin not belonging to this component!");
89                 if (pinsByName.containsKey(pin.name))
90                         throw new IllegalArgumentException("Duplicate pin name: " + pin.name);
91                 pinsByName.put(pin.name, pin);
92                 callPinAddedListeners(pin);
93                 pin.addRedrawListener(redrawListenerForSubcomponents);
94                 requestRedraw();
95         }
96
97         /**
98          * Removes the given pin from this component and calls pinAddedListeners and redrawListeners.
99          * 
100          * @throws NullPointerException if there was no pin with this name
101          * 
102          * @author Daniel Kirschten
103          */
104         protected void removePin(String name)
105         {
106                 Pin pin = pinsByName.remove(name);
107                 callPinRemovedListeners(pin);
108                 pin.removeRedrawListener(redrawListenerForSubcomponents);
109                 requestRedraw();
110         }
111
112         /**
113          * Returns a collection of pins of this component.
114          * 
115          * @author Daniel Kirschten
116          */
117         public Map<String, Pin> getPins()
118         {
119                 return pinsUnmodifiable;
120         }
121
122         /**
123          * Returns the pin with the given name of this component.
124          * 
125          * @throws IllegalArgumentException if there is no pin with the given name
126          * 
127          * @author Daniel Kirschten
128          */
129         public Pin getPin(String name)
130         {
131                 Pin pin = pinsByName.get(name);
132                 if (pin == null)
133                         throw new IllegalArgumentException("No pin with the name " + name);
134                 return pin;
135         }
136
137         // high-level access
138
139         /**
140          * Sets the given high-level state to the given value. <br>
141          * A high level state ID consists of parts separated by dots ('.').<br>
142          * The last part (the part after the last dot) is called "atomic high level state ID". The parts before that part are called
143          * "subcomponent ID"s.<br>
144          * If there is no dot in a high level state ID, the whole high level state ID is called atomic.<br>
145          * Note that subcomponent IDs don't have to correspond to actual subcomponents. For example, a RAM component may supply subcomponent IDs
146          * "c0000", "c0001" ... "cFFFF" without actually having a subcomponent for each cell. It also is allowed for an atomic high level state
147          * ID to be delegated to a subcomponent.
148          * 
149          * @author Daniel Kirschten
150          */
151         @SuppressWarnings({ "static-method", "unused" }) // this method is intended to be overridden
152         public void setHighLevelState(String stateID, Object newState)
153         {
154                 throw new IllegalArgumentException("No high level state with ID " + stateID);
155         }
156
157         /**
158          * Gets the current value of the given high-level state. <br>
159          * See {@link #setHighLevelState(String, Object)} for an explanation of high-level state IDs.
160          * 
161          * @author Daniel Kirschten
162          */
163         @SuppressWarnings("static-method") // this method is intended to be overridden
164         public Object getHighLevelState(String stateID)
165         {
166                 throw new IllegalArgumentException("No high level state with ID " + stateID);
167         }
168
169         // "graphical" operations
170
171         /**
172          * Sets the position of this component and calls componentMovedListeners and redrawListeners.
173          * 
174          * @author Daniel Kirschten
175          */
176         public void moveTo(double x, double y)
177         {
178                 bounds.x = x;
179                 bounds.y = y;
180                 callComponentMovedListeners();
181                 requestRedraw();
182         }
183
184         /**
185          * Sets the size of this component and calls redrawListeners.
186          * 
187          * @author Daniel Kirschten
188          */
189         protected void setSize(double width, double height)
190         {
191                 bounds.width = width;
192                 bounds.height = height;
193                 requestRedraw();
194         }
195
196         /**
197          * Returns the bounds of this component. Is a bit slower than {@link #getPosX()}, {@link #getPosY()}, {@link #getWidth},
198          * {@link #getHeight}, because new objects are created.
199          * 
200          * @author Daniel Kirschten
201          */
202         public final Rectangle getBounds()
203         {
204                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
205         }
206
207         /**
208          * Returns the x coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
209          * 
210          * @author Daniel Kirschten
211          */
212         public double getPosX()
213         {
214                 return bounds.x;
215         }
216
217         /**
218          * Returns the y coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
219          * 
220          * @author Daniel Kirschten
221          */
222         public double getPosY()
223         {
224                 return bounds.y;
225         }
226
227         /**
228          * Returns the (graphical) width of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
229          * 
230          * @author Daniel Kirschten
231          */
232         public double getWidth()
233         {
234                 return bounds.width;
235         }
236
237         /**
238          * Returns the height of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
239          * 
240          * @author Daniel Kirschten
241          */
242         public double getHeight()
243         {
244                 return bounds.height;
245         }
246
247         /**
248          * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
249          * click.
250          * 
251          * @author Daniel Kirschten
252          */
253         @SuppressWarnings({ "static-method", "unused" }) // this method is inteded to be overridden
254         public boolean clicked(double x, double y)
255         {
256                 return false;
257         }
258
259         /**
260          * Render this component to the given gc, in absoulute coordinates.
261          * 
262          * @author Daniel Kirschten
263          */
264         public abstract void render(GeneralGC gc, Rectangle visibleRegion);
265
266         // listeners
267
268         /**
269          * Calls redraw listeners.
270          * 
271          * @author Daniel Kirschten
272          */
273         protected void requestRedraw()
274         {
275                 callRedrawListeners();
276         }
277
278         // @formatter:off
279         public void addComponentMovedListener   (Consumer<? super GUIComponent> listener) {componentMovedListeners.add   (listener);}
280         public void addPinAddedListener         (Consumer<? super Pin         > listener) {pinAddedListeners      .add   (listener);}
281         public void addPinRemovedListener       (Consumer<? super Pin         > listener) {pinRemovedListeners    .add   (listener);}
282         public void addRedrawListener           (Runnable                       listener) {redrawListeners        .add   (listener);}
283
284         public void removeComponentMovedListener(Consumer<? super GUIComponent> listener) {componentMovedListeners .remove(listener);}
285         public void removePinAddedListener      (Consumer<? super Pin         > listener) {pinAddedListeners       .remove(listener);}
286         public void removePinRemovedListener    (Consumer<? super Pin         > listener) {pinRemovedListeners     .remove(listener);}
287         public void removeRedrawListener        (Runnable                       listener) {redrawListeners         .remove(listener);}
288
289         private void callComponentMovedListeners(     ) {componentMovedListeners.forEach(l -> l.accept(this));}
290         private void callPinAddedListeners      (Pin p) {pinAddedListeners      .forEach(l -> l.accept(p   ));}
291         private void callPinRemovedListeners    (Pin p) {pinRemovedListeners    .forEach(l -> l.accept(p   ));}
292         private void callRedrawListeners        (     ) {redrawListeners        .forEach(l -> l.run(       ));}
293         // @formatter:on
294 }