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