1 /*
2  * Copyright (c) 2018 Helmut Neemann
3  * Use of this source code is governed by the GPL v3 license
4  * that can be found in the LICENSE file.
5  */
6 package de.neemann.digital.draw.shapes.custom;
7 
8 import de.neemann.digital.core.ObservableValue;
9 import de.neemann.digital.core.ObservableValues;
10 import de.neemann.digital.core.element.PinDescription;
11 import de.neemann.digital.draw.elements.Circuit;
12 import de.neemann.digital.draw.elements.PinException;
13 import de.neemann.digital.draw.graphics.*;
14 import de.neemann.digital.draw.graphics.Polygon;
15 import de.neemann.digital.draw.shapes.Drawable;
16 import de.neemann.digital.lang.Lang;
17 
18 import java.awt.*;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 
24 /**
25  * Is intended to be stored in a file.
26  */
27 public final class CustomShapeDescription implements Iterable<CustomShapeDescription.Holder> {
28 
29     private final HashMap<String, Pin> pins;
30     private final ArrayList<Holder> drawables;
31     private final TextHolder label;
32 
33     /**
34      * Creates a new instance
35      */
CustomShapeDescription(HashMap<String, Pin> pins, ArrayList<Holder> drawables, TextHolder label)36     private CustomShapeDescription(HashMap<String, Pin> pins, ArrayList<Holder> drawables, TextHolder label) {
37         this.pins = pins;
38         this.drawables = drawables;
39         this.label = label;
40     }
41 
42     /**
43      * Returns the position of the given pin.
44      *
45      * @param name the name of the pin
46      * @return the position of the pin
47      * @throws PinException thrown if pin is not found
48      */
getPin(String name)49     Pin getPin(String name) throws PinException {
50         final Pin pin = pins.get(name);
51         if (pin == null)
52             throw new PinException(Lang.get("err_customShapeHasNoPin_N", name));
53         return pin;
54     }
55 
56     @Override
iterator()57     public Iterator<Holder> iterator() {
58         return drawables.iterator();
59     }
60 
61     /**
62      * Transforms this custom shape
63      *
64      * @param tr the transformation
65      */
transform(Transform tr)66     public void transform(Transform tr) {
67         for (Holder h : drawables)
68             h.transform(tr);
69         for (Pin p : pins.values())
70             p.transform(tr);
71         if (label != null)
72             label.transform(tr);
73     }
74 
75     /**
76      * @return the number of pins in this shape
77      */
getPinCount()78     public int getPinCount() {
79         return pins.size();
80     }
81 
82     /**
83      * @return the TextHolder used to draw the label, maybe null
84      */
getLabel()85     public TextHolder getLabel() {
86         return label;
87     }
88 
89     /**
90      * @return the dfined pins
91      */
getPins()92     public Collection<Pin> getPins() {
93         return pins.values();
94     }
95 
96     /**
97      * @return true if shape is empty
98      */
isEmpty()99     public boolean isEmpty() {
100         return drawables.isEmpty() && pins.isEmpty();
101     }
102 
103     /**
104      * Checks the compatibility of this shape to the given circuit
105      *
106      * @param circuit the circuit
107      * @throws PinException PinException
108      */
checkCompatibility(Circuit circuit)109     public void checkCompatibility(Circuit circuit) throws PinException {
110         final ObservableValues outputNames = circuit.getOutputNames();
111         for (ObservableValue out : outputNames)
112             getPin(out.getName());
113         final PinDescription[] inputNames = circuit.getInputNames();
114         for (PinDescription in : inputNames)
115             getPin(in.getName());
116 
117         int pinsNum = outputNames.size() + inputNames.length;
118         if (pinsNum != pins.size())
119             throw new PinException(Lang.get("err_morePinsDefinedInSVGAsNeeded"));
120     }
121 
122     /*
123      * Two CustomShapeDescriptions are equal if and only if they are both empty!
124      */
125     @Override
equals(Object o)126     public boolean equals(Object o) {
127         if (this == o) return true;
128         if (o == null || getClass() != o.getClass()) return false;
129 
130         CustomShapeDescription customShapeDescription = (CustomShapeDescription) o;
131 
132         return customShapeDescription.isEmpty() && isEmpty();
133     }
134 
135     @Override
hashCode()136     public int hashCode() {
137         if (isEmpty())
138             return 0;
139         return super.hashCode();
140     }
141 
142     private interface Transformable {
transform(Transform tr)143         void transform(Transform tr);
144     }
145 
146     interface Holder extends Drawable, Transformable {
147     }
148 
149     /**
150      * Stores a line.
151      */
152     public static final class LineHolder implements Holder {
153         private Vector p1;
154         private Vector p2;
155         private final int thickness;
156         private final Color color;
157 
LineHolder(Vector p1, Vector p2, int thickness, Color color)158         private LineHolder(Vector p1, Vector p2, int thickness, Color color) {
159             this.p1 = p1;
160             this.p2 = p2;
161             this.thickness = thickness;
162             this.color = color;
163         }
164 
165         @Override
drawTo(Graphic graphic, Style highLight)166         public void drawTo(Graphic graphic, Style highLight) {
167             graphic.drawLine(p1, p2, Style.NORMAL.deriveStyle(thickness, false, color));
168         }
169 
170         /**
171          * @return first coordinate
172          */
getP1()173         public VectorInterface getP1() {
174             return p1;
175         }
176 
177         /**
178          * @return second coordinate
179          */
getP2()180         public VectorInterface getP2() {
181             return p2;
182         }
183 
184         @Override
transform(Transform tr)185         public void transform(Transform tr) {
186             p1 = p1.transform(tr).round();
187             p2 = p2.transform(tr).round();
188         }
189     }
190 
191     /**
192      * Stores a circle
193      */
194     public static final class CircleHolder implements Holder {
195         private Vector p1;
196         private Vector p2;
197         private final int thickness;
198         private final Color color;
199         private final boolean filled;
200 
201 
CircleHolder(Vector p1, Vector p2, int thickness, Color color, boolean filled)202         private CircleHolder(Vector p1, Vector p2, int thickness, Color color, boolean filled) {
203             this.p1 = p1;
204             this.p2 = p2;
205             this.thickness = thickness;
206             this.color = color;
207             this.filled = filled;
208         }
209 
210         @Override
drawTo(Graphic graphic, Style highLight)211         public void drawTo(Graphic graphic, Style highLight) {
212             graphic.drawCircle(p1, p2, Style.NORMAL.deriveStyle(thickness, filled, color));
213         }
214 
215         /**
216          * @return first coordinate
217          */
getP1()218         public VectorInterface getP1() {
219             return p1;
220         }
221 
222         /**
223          * @return second coordinate
224          */
getP2()225         public VectorInterface getP2() {
226             return p2;
227         }
228 
229         @Override
transform(Transform tr)230         public void transform(Transform tr) {
231             p1 = p1.transform(tr).round();
232             p2 = p2.transform(tr).round();
233         }
234     }
235 
236     /**
237      * Stores a polygon
238      */
239     public static final class PolygonHolder implements Holder {
240         private Polygon poly;
241         private final int thickness;
242         private final boolean filled;
243         private final Color color;
244 
PolygonHolder(Polygon poly, int thickness, boolean filled, Color color)245         private PolygonHolder(Polygon poly, int thickness, boolean filled, Color color) {
246             this.poly = poly;
247             this.thickness = thickness;
248             this.filled = filled;
249             this.color = color;
250         }
251 
252         @Override
drawTo(Graphic graphic, Style highLight)253         public void drawTo(Graphic graphic, Style highLight) {
254             graphic.drawPolygon(poly, Style.NORMAL.deriveStyle(thickness, filled, color));
255         }
256 
257         /**
258          * @return the stored polygon
259          */
getPolygon()260         public Polygon getPolygon() {
261             return poly;
262         }
263 
264         @Override
transform(Transform tr)265         public void transform(Transform tr) {
266             poly = poly.transform(tr);
267         }
268     }
269 
270     /**
271      * Stores a text
272      */
273     public static final class TextHolder implements Holder {
274         private Vector p1;
275         private Vector p2;
276         private final String text;
277         private final Orientation orientation;
278         private final int size;
279         private final Color color;
280 
TextHolder(Vector p1, Vector p2, String text, Orientation orientation, int size, Color color)281         private TextHolder(Vector p1, Vector p2, String text, Orientation orientation, int size, Color color) {
282             this.p1 = p1;
283             this.p2 = p2;
284             this.text = text;
285             this.orientation = orientation;
286             this.size = size;
287             this.color = color;
288         }
289 
290         @Override
drawTo(Graphic graphic, Style highLight)291         public void drawTo(Graphic graphic, Style highLight) {
292             drawText(graphic, text);
293         }
294 
295         /**
296          * Draws the given text to the given graphic instance
297          *
298          * @param graphic the graphic instance to draw to
299          * @param text    the text to draw
300          */
drawText(Graphic graphic, String text)301         public void drawText(Graphic graphic, String text) {
302             graphic.drawText(p1, p2, text, orientation,
303                     Style.NORMAL
304                             .deriveFontStyle(size, true)
305                             .deriveColor(color));
306         }
307 
308         @Override
transform(Transform tr)309         public void transform(Transform tr) {
310             p1 = p1.transform(tr).round();
311             p2 = p2.transform(tr).round();
312         }
313 
314         /**
315          * @return the text position
316          */
getPos()317         public Vector getPos() {
318             return p1;
319         }
320 
321         /**
322          * @return the font size
323          */
getFontSize()324         public int getFontSize() {
325             return size;
326         }
327 
328         /**
329          * @return the text string
330          */
getText()331         public String getText() {
332             return text;
333         }
334     }
335 
336     /**
337      * Describes a pin position
338      */
339     public static final class Pin implements Transformable {
340         private Vector pos;
341         private boolean showLabel;
342 
Pin(Vector pos, boolean showLabel)343         private Pin(Vector pos, boolean showLabel) {
344             this.pos = pos;
345             this.showLabel = showLabel;
346         }
347 
isShowLabel()348         boolean isShowLabel() {
349             return showLabel;
350         }
351 
352         /**
353          * @return the position of the pin
354          */
getPos()355         public Vector getPos() {
356             return pos;
357         }
358 
359         @Override
transform(Transform tr)360         public void transform(Transform tr) {
361             pos = pos.transform(tr).round();
362         }
363     }
364 
365     /**
366      * Used to build a custom shape description
367      */
368     public static final class Builder {
369         private final HashMap<String, Pin> pins;
370         private final ArrayList<Holder> drawables;
371         private TextHolder label;
372 
373         /**
374          * Creates a new builder
375          */
Builder()376         public Builder() {
377             pins = new HashMap<>();
378             drawables = new ArrayList<>();
379         }
380 
381         /**
382          * Sets the label positioning info.
383          *
384          * @param pos0            pos0
385          * @param pos1            pos1
386          * @param textOrientation textOrientation
387          * @param fontSize        fontSize
388          * @param filled          filled
389          * @return this for chained calls
390          */
setLabel(Vector pos0, Vector pos1, Orientation textOrientation, int fontSize, Color filled)391         public Builder setLabel(Vector pos0, Vector pos1, Orientation textOrientation, int fontSize, Color filled) {
392             label = new TextHolder(pos0, pos1, "", textOrientation, fontSize, filled);
393             return this;
394         }
395 
396         /**
397          * Adds a pin to this shape description
398          *
399          * @param name      the name of the pin
400          * @param pos       the pins position
401          * @param showLabel if true the label of the pin is shown
402          * @return this for chained calls
403          */
addPin(String name, Vector pos, boolean showLabel)404         public Builder addPin(String name, Vector pos, boolean showLabel) {
405             pins.put(name, new Pin(pos, showLabel));
406             return this;
407         }
408 
409 
410         /**
411          * Adds a polygon to the shape
412          *
413          * @param p1        starting point of the line
414          * @param p2        ending point of the line
415          * @param thickness the line thickness
416          * @param color     the color to use
417          * @return this for chained calls
418          */
addLine(Vector p1, Vector p2, int thickness, Color color)419         public Builder addLine(Vector p1, Vector p2, int thickness, Color color) {
420             drawables.add(new LineHolder(p1, p2, thickness, color));
421             return this;
422         }
423 
424         /**
425          * Adds a circle to the shape
426          *
427          * @param p1        upper left corner of the circles bounding box
428          * @param p2        lower right corner of the circles bounding box
429          * @param thickness the line thickness
430          * @param color     the color to use
431          * @param filled    true if filled
432          * @return this for chained calls
433          */
addCircle(Vector p1, Vector p2, int thickness, Color color, boolean filled)434         public Builder addCircle(Vector p1, Vector p2, int thickness, Color color, boolean filled) {
435             drawables.add(new CircleHolder(p1, p2, thickness, color, filled));
436             return this;
437         }
438 
439         /**
440          * Adds a polygon to the shape
441          *
442          * @param poly      the polygon to add
443          * @param thickness the line thickness
444          * @param color     the color to use
445          * @param filled    true if filled
446          * @return this for chained calls
447          */
addPolygon(Polygon poly, int thickness, Color color, boolean filled)448         public Builder addPolygon(Polygon poly, int thickness, Color color, boolean filled) {
449             drawables.add(new PolygonHolder(poly, thickness, filled, color));
450             return this;
451         }
452 
453         /**
454          * Adds a text to the shape
455          *
456          * @param p1          position
457          * @param p2          second position to determin the base line orientation
458          * @param text        the text to draw
459          * @param orientation the orientation of the text
460          * @param size        the font size
461          * @param color       the text color
462          * @return this for chained calls
463          */
addText(Vector p1, Vector p2, String text, Orientation orientation, int size, Color color)464         public Builder addText(Vector p1, Vector p2, String text, Orientation orientation, int size, Color color) {
465             drawables.add(new TextHolder(p1, p2, text, orientation, size, color));
466             return this;
467         }
468 
469         /**
470          * @return the {@link CustomShapeDescription}
471          */
build()472         public CustomShapeDescription build() {
473             return new CustomShapeDescription(pins, drawables, label);
474         }
475 
476     }
477 }
478