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