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