Fixed & cleaned up GUIWire:
[Mograsim.git] / net.mograsim.logic.model / src / net / mograsim / logic / model / model / wires / GUIWire.java
1 package net.mograsim.logic.model.model.wires;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.List;
6 import java.util.function.Consumer;
7
8 import org.eclipse.swt.SWT;
9
10 import net.haspamelodica.swt.helper.gcs.GeneralGC;
11 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
12 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
13 import net.mograsim.logic.core.LogicObserver;
14 import net.mograsim.logic.core.types.BitVector;
15 import net.mograsim.logic.core.types.BitVectorFormatter;
16 import net.mograsim.logic.core.wires.Wire;
17 import net.mograsim.logic.core.wires.Wire.ReadEnd;
18 import net.mograsim.logic.model.model.ViewModelModifiable;
19 import net.mograsim.preferences.ColorDefinition;
20 import net.mograsim.preferences.ColorManager;
21
22 /**
23  * A wire connecting exactly two {@link Pin}s.
24  * 
25  * @author Daniel Kirschten
26  */
27 public class GUIWire
28 {
29         /**
30          * The model this wire is a part of.
31          */
32         private final ViewModelModifiable model;
33         /**
34          * The logical width of this wire. Is equal to the logical with of {@link #pin1} and {@link #pin2}.
35          */
36         public final int logicWidth;
37         /**
38          * The {@link Pin} on one side of this wire, usually the signal source.
39          */
40         private Pin pin1;
41         /**
42          * The {@link Pin} on one side of this wire, usually the signal target.
43          */
44         private Pin pin2;
45         /**
46          * The user-defined path between {@link #pin1} and {@link #pin2}.<br>
47          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any
48          * interpolation".
49          */
50         private Point[] path;
51         /**
52          * The bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})
53          */
54         private final Rectangle bounds;
55         /**
56          * The effective path of this wire, including automatic interpolation and the position of both {@link Pin}s. Is never null.
57          */
58         private double[] effectivePath;
59
60         private final List<Runnable> redrawListeners;
61         private final List<Consumer<GUIWire>> pathChangedListeners;
62
63         /**
64          * A LogicObserver calling redrawListeners. Used for logic model bindings.
65          */
66         private final LogicObserver logicObs;
67         /**
68          * A ReadEnd of the logic wire this GUI wire currently is bound to.
69          */
70         private ReadEnd end;
71
72         // creation and destruction
73
74         /**
75          * Creates a new {@link GUIWire} with automatic interpolation.
76          * 
77          * @author Daniel Kirschten
78          */
79         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2)
80         {
81                 this(model, pin1, pin2, (Point[]) null);
82         }
83
84         /**
85          * Creates a new {@link GUIWire} with automatic interpolation.
86          * 
87          * @author Daniel Kirschten
88          */
89         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2)
90         {
91                 this(model, pin1, pin2, (Point[]) null);
92         }
93
94         /**
95          * Creates a new {@link GUIWire} with automatic interpolation.
96          * 
97          * @author Daniel Kirschten
98          */
99         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2)
100         {
101                 this(model, pin1, pin2, (Point[]) null);
102         }
103
104         /**
105          * Creates a new {@link GUIWire} with automatic interpolation.
106          * 
107          * @author Daniel Kirschten
108          */
109         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2)
110         {
111                 this(model, pin1, pin2, (Point[]) null);
112         }
113
114         /**
115          * Creates a new {@link GUIWire} without automatic interpolation.
116          * 
117          * @author Daniel Kirschten
118          */
119         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2, Point... path)
120         {
121                 this(model, pin1.getPin(), pin2.getPin(), path);
122         }
123
124         /**
125          * Creates a new {@link GUIWire} without automatic interpolation.
126          * 
127          * @author Daniel Kirschten
128          */
129         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2, Point... path)
130         {
131                 this(model, pin1.getPin(), pin2, path);
132         }
133
134         /**
135          * Creates a new {@link GUIWire} without automatic interpolation.
136          * 
137          * @author Daniel Kirschten
138          */
139         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2, Point... path)
140         {
141                 this(model, pin1, pin2.getPin(), path);
142         }
143
144         /**
145          * Creates a new {@link GUIWire} without automatic interpolation.
146          * 
147          * @author Daniel Kirschten
148          */
149         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2, Point... path)
150         {
151                 this.model = model;
152                 this.logicWidth = pin1.logicWidth;
153                 if (pin2.logicWidth != pin1.logicWidth)
154                         throw new IllegalArgumentException("Can't connect pins of different logic width");
155
156                 this.pin1 = pin1;
157                 this.pin2 = pin2;
158
159                 this.path = path == null ? null : Arrays.copyOf(path, path.length);
160                 this.bounds = new Rectangle(0, 0, -1, -1);
161
162                 redrawListeners = new ArrayList<>();
163                 pathChangedListeners = new ArrayList<>();
164
165                 logicObs = (i) -> callRedrawListeners();
166
167                 pin1.addPinMovedListener(p -> pinMoved());
168                 pin2.addPinMovedListener(p -> pinMoved());
169
170                 recalculateEffectivePath();
171
172                 model.wireCreated(this);
173         }
174
175         /**
176          * Destroys this wire. This method implicitly calls {@link ViewModelModifiable#wireDestroyed(GUIWire) wireDestroyed()} for the model
177          * this component is a part of.
178          * 
179          * @author Daniel Kirschten
180          */
181         public void destroy()
182         {
183                 model.wireDestroyed(this);
184         }
185
186         // pins
187
188         /**
189          * Returns the {@link Pin} on one side of this wire, usually the signal source.
190          * 
191          * @author Daniel Kirschten
192          */
193         public Pin getPin1()
194         {
195                 return pin1;
196         }
197
198         /**
199          * Returns the {@link Pin} on one side of this wire, usually the signal target.
200          * 
201          * @author Daniel Kirschten
202          */
203         public Pin getPin2()
204         {
205                 return pin2;
206         }
207
208         /**
209          * Called when {@link #pin1} or {@link #pin2} were moved.
210          * 
211          * @author Daniel Kirschten
212          */
213         private void pinMoved()
214         {
215                 recalculateEffectivePath();
216                 callRedrawListeners();
217         }
218
219         // "graphical" operations
220
221         /**
222          * Recalculates {@link #effectivePath} "from scratch". Also updates {@link #bounds}.
223          * 
224          * @author Daniel Kirschten
225          */
226         private void recalculateEffectivePath()
227         {
228                 Point pos1 = pin1.getPos(), pos2 = pin2.getPos();
229
230                 double boundsX1 = Math.min(pos1.x, pos2.x);
231                 double boundsY1 = Math.min(pos1.y, pos2.y);
232                 double boundsX2 = Math.max(pos1.x, pos2.x);
233                 double boundsY2 = Math.max(pos1.y, pos2.y);
234
235                 if (path == null)
236                         effectivePath = new double[] { pos1.x, pos1.y, (pos1.x + pos2.x) / 2, pos1.y, (pos1.x + pos2.x) / 2, pos2.y, pos2.x, pos2.y };
237                 else
238                 {
239                         effectivePath = new double[path.length * 2 + 4];
240                         effectivePath[0] = pos1.x;
241                         effectivePath[1] = pos1.y;
242                         for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2)
243                         {
244                                 double pathX = path[srcI].x;
245                                 double pathY = path[srcI].y;
246                                 effectivePath[dstI + 0] = pathX;
247                                 effectivePath[dstI + 1] = pathY;
248                                 if (pathX < boundsX1)
249                                         boundsX1 = pathX;
250                                 if (pathX > boundsX2)
251                                         boundsX2 = pathX;
252                                 if (pathY < boundsY1)
253                                         boundsY1 = pathY;
254                                 if (pathY > boundsY2)
255                                         boundsY2 = pathY;
256                         }
257                         effectivePath[effectivePath.length - 2] = pos2.x;
258                         effectivePath[effectivePath.length - 1] = pos2.y;
259                 }
260
261                 bounds.x = boundsX1;
262                 bounds.y = boundsY1;
263                 bounds.width = boundsX2 - boundsX1;
264                 bounds.height = boundsY2 - boundsY1;
265         }
266
267         /**
268          * Returns the bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})
269          * 
270          * @author Daniel Kirschten
271          */
272         public Rectangle getBounds()
273         {
274                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
275         }
276
277         /**
278          * Render this wire to the given gc, in absoulute coordinates.
279          * 
280          * @author Daniel Kirschten
281          */
282         public void render(GeneralGC gc)
283         {
284                 ColorDefinition wireColor = BitVectorFormatter.formatAsColor(end);
285                 if (wireColor != null)
286                         gc.setForeground(ColorManager.current().toColor(wireColor));
287                 gc.drawPolyline(effectivePath);
288         }
289
290         // operations concerning the path
291
292         /**
293          * The user-defined path between {@link #pin1} and {@link #pin2}. Note that this is not neccessarily equal to the effective path drawn
294          * in {@link #render(GeneralGC)}.<br>
295          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any
296          * interpolation".
297          * 
298          * @author Daniel Kirschten
299          */
300         public Point[] getPath()
301         {
302                 return deepPathCopy(path);
303         }
304
305         public void setPath(Point... path)
306         {
307                 this.path = deepPathCopy(path);
308                 recalculateEffectivePath();
309                 callPathChangedListeners();
310                 callRedrawListeners();
311         }
312
313         public Point getPathPoint(int index)
314         {
315                 return pointCopy(path[index]);
316         }
317
318         public void setPathPoint(Point p, int index)
319         {
320                 path[index] = pointCopy(p);
321                 recalculateEffectivePath();
322                 callPathChangedListeners();
323                 callRedrawListeners();
324         }
325
326         public void insertPathPoint(Point p, int index)
327         {
328                 if (path == null)
329                         path = new Point[] { pointCopy(p) };
330                 else
331                 {
332                         Point[] oldPath = path;
333                         path = new Point[oldPath.length + 1];
334                         System.arraycopy(oldPath, 0, path, 0, index);
335                         if (index < oldPath.length)
336                                 System.arraycopy(oldPath, index, path, index + 1, oldPath.length - index);
337                         path[index] = pointCopy(p);
338                 }
339         }
340
341         public void removePathPoint(int index)
342         {
343                 if (path.length == 0)
344                         path = null;
345                 else
346                 {
347                         Point[] oldPath = path;
348                         path = new Point[oldPath.length - 1];
349                         System.arraycopy(oldPath, 0, path, 0, index);
350                         if (index < oldPath.length - 1)
351                                 System.arraycopy(oldPath, index + 1, path, index, oldPath.length - index - 1);
352                 }
353         }
354
355         public double[] getEffectivePath()
356         {
357                 return Arrays.copyOf(effectivePath, effectivePath.length);
358         }
359
360         private static Point[] deepPathCopy(Point[] path)
361         {
362                 if (path == null)
363                         return null;
364                 Point[] copy = new Point[path.length];
365                 for (int i = 0; i < path.length; i++)
366                         copy[i] = pointCopy(path[i]);
367                 return copy;
368         }
369
370         private static Point pointCopy(Point p)
371         {
372                 return new Point(p.x, p.y);
373         }
374
375         // logic model binding
376
377         /**
378          * Binds this {@link GUIWire} to the given {@link ReadEnd}: The color of this {@link GUIWire} will now depend on the state of the given
379          * {@link ReadEnd}, and further changes of the given {@link ReadEnd} will result in readrawListeners being called.<br>
380          * The argument can be null, in which case the old binding is stopped.
381          * 
382          * @author Daniel Kirschten
383          */
384         public void setLogicModelBinding(ReadEnd end)
385         {
386                 if (this.end != null)
387                         this.end.deregisterObserver(logicObs);
388                 this.end = end;
389                 if (end != null)
390                         end.registerObserver(logicObs);
391         }
392
393         /**
394          * Returns whether this {@link GUIWire} has a logic model binding or not.
395          * 
396          * @author Daniel Kirschten
397          */
398         public boolean hasLogicModelBinding()
399         {
400                 return end != null;
401         }
402
403         /**
404          * If this {@link GUIWire} has a logic model binding, delegates to {@link Wire#forceValues(BitVector)} for the {@link Wire}
405          * corresponding to this {@link GUIWire}.
406          * 
407          * @author Daniel Kirschten
408          */
409         public void forceWireValues(BitVector values)
410         {
411                 end.getWire().forceValues(values);
412         }
413
414         /**
415          * If this {@link GUIWire} has a logic model binding, delegates to {@link ReadEnd#getValues()} for the {@link ReadEnd} corresponding to
416          * this {@link GUIWire}.
417          * 
418          * @author Daniel Kirschten
419          */
420         public BitVector getWireValues()
421         {
422                 return end.getValues();
423         }
424
425         // listeners
426
427         // @formatter:off
428         public void addRedrawListener        (Runnable          listener) {redrawListeners     .add    (listener);}
429         public void addPathChangedListener   (Consumer<GUIWire> listener) {pathChangedListeners.add    (listener);}
430
431         public void removeRedrawListener     (Runnable          listener) {redrawListeners     .remove(listener);}
432         public void removePathChangedListener(Consumer<GUIWire> listener) {pathChangedListeners.remove(listener);}
433
434         private void callRedrawListeners     () {redrawListeners     .forEach(l -> l.run   (    ));}
435         private void callPathChangedListeners() {pathChangedListeners.forEach(l -> l.accept(this));}
436         // @formatter:on
437
438         @Override
439         public String toString()
440         {
441                 return "GUIWire [" + pin1 + "---" + pin2 + ", value=" + (end == null ? "null" : end.getValues()) + "]";
442         }
443 }