6edefbb7ac879af3519510a05e8ef2b2a50a938b
[Mograsim.git] / net.mograsim.logic.ui / src / net / mograsim / logic / ui / model / wires / GUIWire.java
1 package net.mograsim.logic.ui.model.wires;\r
2 \r
3 import java.util.ArrayList;\r
4 import java.util.Arrays;\r
5 import java.util.List;\r
6 \r
7 import org.eclipse.swt.SWT;\r
8 \r
9 import net.haspamelodica.swt.helper.gcs.GeneralGC;\r
10 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;\r
11 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;\r
12 import net.mograsim.logic.core.LogicObserver;\r
13 import net.mograsim.logic.core.types.BitVectorFormatter;\r
14 import net.mograsim.logic.core.wires.Wire.ReadEnd;\r
15 import net.mograsim.logic.ui.ColorHelper;\r
16 import net.mograsim.logic.ui.model.ViewModelModifiable;\r
17 \r
18 /**\r
19  * A wire connecting exactly two {@link Pin}s.\r
20  * \r
21  * @author Daniel Kirschten\r
22  */\r
23 public class GUIWire\r
24 {\r
25         /**\r
26          * The model this wire is a part of.\r
27          */\r
28         private final ViewModelModifiable model;\r
29         /**\r
30          * The logical width of this wire. Is equal to the logical with of {@link #pin1} and {@link #pin2}.\r
31          */\r
32         public final int logicWidth;\r
33         /**\r
34          * The {@link Pin} on one side of this wire, usually the signal source.\r
35          */\r
36         private Pin pin1;\r
37         /**\r
38          * The {@link Pin} on one side of this wire, usually the signal target.\r
39          */\r
40         private Pin pin2;\r
41         /**\r
42          * The user-defined path between {@link #pin1} and {@link #pin2}.<br>\r
43          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any\r
44          * interpolation".\r
45          */\r
46         private Point[] path;\r
47         /**\r
48          * The bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})\r
49          */\r
50         private final Rectangle bounds;\r
51         /**\r
52          * The effective path of this wire, including automatic interpolation and the position of both {@link Pin}s. Is never null.\r
53          */\r
54         private double[] effectivePath;\r
55 \r
56         private final List<Runnable> redrawListeners;\r
57 \r
58         /**\r
59          * A LogicObserver calling redrawListeners. Used for logic model bindings.\r
60          */\r
61         private final LogicObserver logicObs;\r
62         /**\r
63          * A ReadEnd of the logic wire this GUI wire currently is bound to.\r
64          */\r
65         private ReadEnd end;\r
66 \r
67         // creation and destruction\r
68 \r
69         /**\r
70          * Creates a new {@link GUIWire} with automatic interpolation.\r
71          * \r
72          * @author Daniel Kirschten\r
73          */\r
74         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2)\r
75         {\r
76                 this(model, pin1, pin2, (Point[]) null);\r
77         }\r
78 \r
79         /**\r
80          * Creates a new {@link GUIWire} with automatic interpolation.\r
81          * \r
82          * @author Daniel Kirschten\r
83          */\r
84         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2)\r
85         {\r
86                 this(model, pin1, pin2, (Point[]) null);\r
87         }\r
88 \r
89         /**\r
90          * Creates a new {@link GUIWire} with automatic interpolation.\r
91          * \r
92          * @author Daniel Kirschten\r
93          */\r
94         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2)\r
95         {\r
96                 this(model, pin1, pin2, (Point[]) null);\r
97         }\r
98 \r
99         /**\r
100          * Creates a new {@link GUIWire} with automatic interpolation.\r
101          * \r
102          * @author Daniel Kirschten\r
103          */\r
104         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2)\r
105         {\r
106                 this(model, pin1, pin2, (Point[]) null);\r
107         }\r
108 \r
109         /**\r
110          * Creates a new {@link GUIWire} without automatic interpolation.\r
111          * \r
112          * @author Daniel Kirschten\r
113          */\r
114         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2, Point... path)\r
115         {\r
116                 this(model, pin1.getPin(), pin2.getPin(), path);\r
117         }\r
118 \r
119         /**\r
120          * Creates a new {@link GUIWire} without automatic interpolation.\r
121          * \r
122          * @author Daniel Kirschten\r
123          */\r
124         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2, Point... path)\r
125         {\r
126                 this(model, pin1.getPin(), pin2, path);\r
127         }\r
128 \r
129         /**\r
130          * Creates a new {@link GUIWire} without automatic interpolation.\r
131          * \r
132          * @author Daniel Kirschten\r
133          */\r
134         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2, Point... path)\r
135         {\r
136                 this(model, pin1, pin2.getPin(), path);\r
137         }\r
138 \r
139         /**\r
140          * Creates a new {@link GUIWire} without automatic interpolation.\r
141          * \r
142          * @author Daniel Kirschten\r
143          */\r
144         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2, Point... path)\r
145         {\r
146                 logicObs = (i) -> callRedrawListeners();\r
147                 this.model = model;\r
148                 this.logicWidth = pin1.logicWidth;\r
149                 if (pin2.logicWidth != pin1.logicWidth)\r
150                         throw new IllegalArgumentException("Can't connect pins of different logic width");\r
151 \r
152                 this.pin1 = pin1;\r
153                 this.pin2 = pin2;\r
154 \r
155                 this.path = path == null ? null : Arrays.copyOf(path, path.length);\r
156                 this.bounds = new Rectangle(0, 0, -1, -1);\r
157 \r
158                 redrawListeners = new ArrayList<>();\r
159 \r
160                 pin1.addPinMovedListener(p -> pinMoved());\r
161                 pin2.addPinMovedListener(p -> pinMoved());\r
162 \r
163                 recalculateEffectivePath();\r
164 \r
165                 model.wireCreated(this);\r
166         }\r
167 \r
168         /**\r
169          * Destroys this wire. This method implicitly calls {@link ViewModelModifiable#wireDestroyed(GUIWire) wireDestroyed()} for the model\r
170          * this component is a part of.\r
171          * \r
172          * @author Daniel Kirschten\r
173          */\r
174         public void destroy()\r
175         {\r
176                 model.wireDestroyed(this);\r
177         }\r
178 \r
179         // pins\r
180 \r
181         /**\r
182          * Returns the {@link Pin} on one side of this wire, usually the signal source.\r
183          * \r
184          * @author Daniel Kirschten\r
185          */\r
186         public Pin getPin1()\r
187         {\r
188                 return pin1;\r
189         }\r
190 \r
191         /**\r
192          * Returns the {@link Pin} on one side of this wire, usually the signal target.\r
193          * \r
194          * @author Daniel Kirschten\r
195          */\r
196         public Pin getPin2()\r
197         {\r
198                 return pin2;\r
199         }\r
200 \r
201         /**\r
202          * Called when {@link #pin1} or {@link #pin2} were moved.\r
203          * \r
204          * @author Daniel Kirschten\r
205          */\r
206         private void pinMoved()\r
207         {\r
208                 recalculateEffectivePath();\r
209                 callRedrawListeners();\r
210         }\r
211 \r
212         // "graphical" operations\r
213 \r
214         /**\r
215          * Recalculates {@link #effectivePath} "from scratch". Also updates {@link #bounds}.\r
216          * \r
217          * @author Daniel Kirschten\r
218          */\r
219         private void recalculateEffectivePath()\r
220         {\r
221                 Point pos1 = pin1.getPos(), pos2 = pin2.getPos();\r
222 \r
223                 double boundsX1 = Math.min(pos1.x, pos2.x);\r
224                 double boundsY1 = Math.min(pos1.y, pos2.y);\r
225                 double boundsX2 = Math.max(pos1.x, pos2.x);\r
226                 double boundsY2 = Math.max(pos1.y, pos2.y);\r
227 \r
228                 if (path == null)\r
229                         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 };\r
230                 else\r
231                 {\r
232                         effectivePath = new double[path.length * 2 + 4];\r
233                         effectivePath[0] = pos1.x;\r
234                         effectivePath[1] = pos1.y;\r
235                         for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2)\r
236                         {\r
237                                 double pathX = path[srcI].x;\r
238                                 double pathY = path[srcI].y;\r
239                                 effectivePath[dstI + 0] = pathX;\r
240                                 effectivePath[dstI + 1] = pathY;\r
241                                 if (pathX < boundsX1)\r
242                                         boundsX1 = pathX;\r
243                                 if (pathX > boundsX2)\r
244                                         boundsX2 = pathX;\r
245                                 if (pathY < boundsY1)\r
246                                         boundsY1 = pathY;\r
247                                 if (pathY > boundsY2)\r
248                                         boundsY2 = pathY;\r
249                         }\r
250                         effectivePath[effectivePath.length - 2] = pos2.x;\r
251                         effectivePath[effectivePath.length - 1] = pos2.y;\r
252                 }\r
253 \r
254                 bounds.x = boundsX1;\r
255                 bounds.y = boundsY1;\r
256                 bounds.width = boundsX2 - boundsX1;\r
257                 bounds.height = boundsY2 - boundsY1;\r
258         }\r
259 \r
260         /**\r
261          * Returns the bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})\r
262          * \r
263          * @author Daniel Kirschten\r
264          */\r
265         public Rectangle getBounds()\r
266         {\r
267                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);\r
268         }\r
269 \r
270         /**\r
271          * Render this wire to the given gc, in absoulute coordinates.\r
272          * \r
273          * @author Daniel Kirschten\r
274          */\r
275         public void render(GeneralGC gc)\r
276         {\r
277                 ColorHelper.executeWithDifferentForeground(gc, BitVectorFormatter.formatAsColor(end), () -> gc.drawPolyline(effectivePath));\r
278         }\r
279 \r
280         /**\r
281          * The user-defined path between {@link #pin1} and {@link #pin2}. Note that this is not neccessarily equal to the effective path drawn\r
282          * in {@link #render(GeneralGC)}.<br>\r
283          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any\r
284          * interpolation".\r
285          * \r
286          * @author Daniel Kirschten\r
287          */\r
288         public Point[] getPath()\r
289         {\r
290                 return path == null ? null : path.clone();\r
291         }\r
292 \r
293         // logic model binding\r
294 \r
295         /**\r
296          * Binds this {@link GUIWire} to the given {@link ReadEnd}: The color of this {@link GUIWire} will now depend on the state of the given\r
297          * {@link ReadEnd}, and further changes of the given {@link ReadEnd} will result in readrawListeners being called.<br>\r
298          * The argument can be null, in which case the old binding is stopped.\r
299          * \r
300          * @author Daniel Kirschten\r
301          */\r
302         public void setLogicModelBinding(ReadEnd end)\r
303         {\r
304                 if (this.end != null)\r
305                         this.end.deregisterObserver(logicObs);\r
306                 this.end = end;\r
307                 if (end != null)\r
308                         end.registerObserver(logicObs);\r
309         }\r
310 \r
311         // listeners\r
312 \r
313         // @formatter:off\r
314         public void addRedrawListener   (Runnable listener) {redrawListeners         .add   (listener);}\r
315 \r
316         public void removeRedrawListener(Runnable listener) {redrawListeners         .remove(listener);}\r
317 \r
318         private void callRedrawListeners() {redrawListeners.forEach(l -> l.run());}\r
319         // @formatter:on\r
320 \r
321 }