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