Fixed issues with WirePointHandle
[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                 recalculateEffectivePath();
425                 callPathChangedListeners();
426         }
427
428         public void removePathPoint(int index)
429         {
430                 if (path.length == 0)
431                         path = null;
432                 else
433                 {
434                         Point[] oldPath = path;
435                         path = new Point[oldPath.length - 1];
436                         System.arraycopy(oldPath, 0, path, 0, index);
437                         if (index < oldPath.length - 1)
438                                 System.arraycopy(oldPath, index + 1, path, index, oldPath.length - index - 1);
439                 }
440                 recalculateEffectivePath();
441                 callPathChangedListeners();
442         }
443
444         public double[] getEffectivePath()
445         {
446                 return Arrays.copyOf(effectivePath, effectivePath.length);
447         }
448
449         private static Point[] deepPathCopy(Point[] path)
450         {
451                 if (path == null)
452                         return null;
453                 Point[] copy = new Point[path.length];
454                 for (int i = 0; i < path.length; i++)
455                         copy[i] = pointCopy(path[i]);
456                 return copy;
457         }
458
459         private static Point pointCopy(Point p)
460         {
461                 return new Point(p.x, p.y);
462         }
463
464         // logic model binding
465
466         /**
467          * Binds this {@link GUIWire} to the given {@link ReadEnd}: The color of this {@link GUIWire} will now depend on the state of the given
468          * {@link ReadEnd}, and further changes of the given {@link ReadEnd} will result in readrawListeners being called.<br>
469          * The argument can be null, in which case the old binding is stopped.
470          * 
471          * @author Daniel Kirschten
472          */
473         public void setLogicModelBinding(ReadEnd end)
474         {
475                 if (this.end != null)
476                         this.end.deregisterObserver(logicObs);
477                 this.end = end;
478                 if (end != null)
479                         end.registerObserver(logicObs);
480         }
481
482         /**
483          * Returns whether this {@link GUIWire} has a logic model binding or not.
484          * 
485          * @author Daniel Kirschten
486          */
487         public boolean hasLogicModelBinding()
488         {
489                 return end != null;
490         }
491
492         /**
493          * If this {@link GUIWire} has a logic model binding, delegates to {@link Wire#forceValues(BitVector)} for the {@link Wire}
494          * corresponding to this {@link GUIWire}.
495          * 
496          * @author Daniel Kirschten
497          */
498         public void forceWireValues(BitVector values)
499         {
500                 end.getWire().forceValues(values);
501         }
502
503         /**
504          * If this {@link GUIWire} has a logic model binding, delegates to {@link ReadEnd#getValues()} for the {@link ReadEnd} corresponding to
505          * this {@link GUIWire}.
506          * 
507          * @author Daniel Kirschten
508          */
509         public BitVector getWireValues()
510         {
511                 return end.getValues();
512         }
513
514         // listeners
515
516         // @formatter:off
517         public void addRedrawListener        (Runnable          listener) {redrawListeners     .add    (listener);}
518         public void addPathChangedListener   (Consumer<GUIWire> listener) {pathChangedListeners.add    (listener);}
519
520         public void removeRedrawListener     (Runnable          listener) {redrawListeners     .remove(listener);}
521         public void removePathChangedListener(Consumer<GUIWire> listener) {pathChangedListeners.remove(listener);}
522
523         private void callRedrawListeners     () {redrawListeners     .forEach(l -> l.run   (    ));}
524         private void callPathChangedListeners() {pathChangedListeners.forEach(l -> l.accept(this));}
525         // @formatter:on
526
527         @Override
528         public String toString()
529         {
530                 return "GUIWire [" + pin1 + "---" + pin2 + ", value=" + (end == null ? "null" : end.getValues()) + "]";
531         }
532 }