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