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