Made wires with logicWidth!=1 thicker
[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 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 GUIWire
29 {
30         /**
31          * The model this wire is a part of.
32          */
33         private final ViewModelModifiable 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 with 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<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                 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 ViewModelModifiable#wireDestroyed(GUIWire) wireDestroyed()} of the model this
261          * 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                 if (logicWidth == 1)
372                         gc.setLineWidth(Preferences.current().getDouble("net.mograsim.logic.model.linewidth.wire.singlebit"));
373                 else
374                         gc.setLineWidth(Preferences.current().getDouble("net.mograsim.logic.model.linewidth.wire.multibit"));
375                 gc.drawPolyline(effectivePath);
376                 gc.setLineWidth(Preferences.current().getDouble("net.mograsim.logic.model.linewidth.default"));
377         }
378
379         // operations concerning the path
380
381         /**
382          * The user-defined path between {@link #pin1} and {@link #pin2}. Note that this is not neccessarily equal to the effective path drawn
383          * in {@link #render(GeneralGC)}.<br>
384          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any
385          * interpolation".
386          * 
387          * @author Daniel Kirschten
388          */
389         public Point[] getPath()
390         {
391                 return deepPathCopy(path);
392         }
393
394         public void setPath(Point... path)
395         {
396                 this.path = deepPathCopy(path);
397                 recalculateEffectivePath();
398                 callPathChangedListeners();
399                 model.requestRedraw();
400         }
401
402         public Point getPathPoint(int index)
403         {
404                 return pointCopy(path[index]);
405         }
406
407         public void setPathPoint(Point p, int index)
408         {
409                 path[index] = pointCopy(p);
410                 recalculateEffectivePath();
411                 callPathChangedListeners();
412                 model.requestRedraw();
413         }
414
415         public void insertPathPoint(Point p, int index)
416         {
417                 if (path == null)
418                         path = new Point[] { pointCopy(p) };
419                 else
420                 {
421                         Point[] oldPath = path;
422                         path = new Point[oldPath.length + 1];
423                         System.arraycopy(oldPath, 0, path, 0, index);
424                         if (index < oldPath.length)
425                                 System.arraycopy(oldPath, index, path, index + 1, oldPath.length - index);
426                         path[index] = pointCopy(p);
427                 }
428                 recalculateEffectivePath();
429                 callPathChangedListeners();
430         }
431
432         public void removePathPoint(int index)
433         {
434                 if (path.length == 0)
435                         path = null;
436                 else
437                 {
438                         Point[] oldPath = path;
439                         path = new Point[oldPath.length - 1];
440                         System.arraycopy(oldPath, 0, path, 0, index);
441                         if (index < oldPath.length - 1)
442                                 System.arraycopy(oldPath, index + 1, path, index, oldPath.length - index - 1);
443                 }
444                 recalculateEffectivePath();
445                 callPathChangedListeners();
446         }
447
448         public double[] getEffectivePath()
449         {
450                 return Arrays.copyOf(effectivePath, effectivePath.length);
451         }
452
453         private static Point[] deepPathCopy(Point[] path)
454         {
455                 if (path == null)
456                         return null;
457                 Point[] copy = new Point[path.length];
458                 for (int i = 0; i < path.length; i++)
459                         copy[i] = pointCopy(path[i]);
460                 return copy;
461         }
462
463         private static Point pointCopy(Point p)
464         {
465                 return new Point(p.x, p.y);
466         }
467
468         // logic model binding
469
470         /**
471          * Binds this {@link GUIWire} to the given {@link ReadEnd}: The color of this {@link GUIWire} will now depend on the state of the given
472          * {@link ReadEnd}, and further changes of the given {@link ReadEnd} will result in readrawListeners being called.<br>
473          * The argument can be null, in which case the old binding is stopped.
474          * 
475          * @author Daniel Kirschten
476          */
477         public void setLogicModelBinding(ReadEnd end)
478         {
479                 if (this.end != null)
480                         this.end.deregisterObserver(logicObs);
481                 this.end = end;
482                 if (end != null)
483                         end.registerObserver(logicObs);
484         }
485
486         /**
487          * Returns whether this {@link GUIWire} has a logic model binding or not.
488          * 
489          * @author Daniel Kirschten
490          */
491         public boolean hasLogicModelBinding()
492         {
493                 return end != null;
494         }
495
496         /**
497          * If this {@link GUIWire} has a logic model binding, delegates to {@link Wire#forceValues(BitVector)} for the {@link Wire}
498          * corresponding to this {@link GUIWire}.
499          * 
500          * @author Daniel Kirschten
501          */
502         public void forceWireValues(BitVector values)
503         {
504                 end.getWire().forceValues(values);
505         }
506
507         /**
508          * If this {@link GUIWire} has a logic model binding, delegates to {@link ReadEnd#getValues()} for the {@link ReadEnd} corresponding to
509          * this {@link GUIWire}.
510          * 
511          * @author Daniel Kirschten
512          */
513         public BitVector getWireValues()
514         {
515                 return end.getValues();
516         }
517
518         // listeners
519
520         // @formatter:off
521         public void addPathChangedListener   (Consumer<GUIWire> listener) {pathChangedListeners.add    (listener);}
522
523         public void removePathChangedListener(Consumer<GUIWire> listener) {pathChangedListeners.remove(listener);}
524
525         private void callPathChangedListeners() {pathChangedListeners.forEach(l -> l.accept(this));}
526         // @formatter:on
527
528         @Override
529         public String toString()
530         {
531                 return "GUIWire [" + pin1 + "---" + pin2 + ", value=" + (end == null ? "null" : end.getValues()) + "]";
532         }
533 }