diff --git a/diylc/diylc-core/src/org/diylc/presenter/update.xml b/diylc/diylc-core/src/org/diylc/presenter/update.xml
index 2449c319..74d5bdaf 100644
--- a/diylc/diylc-core/src/org/diylc/presenter/update.xml
+++ b/diylc/diylc-core/src/org/diylc/presenter/update.xml
@@ -2938,6 +2938,10 @@
NEW_FEATURE
Added "Tag Strip" component for point-to-point layouts
+
+ NEW_FEATURE
+ Added "Duo-Diode" tube symbol
+
IMPROVEMENT
Prevent the app from closing while running background operations, like saving and exporting files
diff --git a/diylc/diylc-library/src/org/diylc/components/tube/DiodeSymbol.java b/diylc/diylc-library/src/org/diylc/components/tube/DiodeSymbol.java
index 2a93fcae..c662a7aa 100644
--- a/diylc/diylc-library/src/org/diylc/components/tube/DiodeSymbol.java
+++ b/diylc/diylc-library/src/org/diylc/components/tube/DiodeSymbol.java
@@ -37,7 +37,7 @@ import org.diylc.core.annotations.ComponentDescriptor;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.annotations.KeywordPolicy;
-@ComponentDescriptor(name = "Diode", author = "Branislav Stojkovic", category = "Schematic Symbols",
+@ComponentDescriptor(name = "Diode Tube", author = "Branislav Stojkovic", category = "Schematic Symbols",
instanceNamePrefix = "V", description = "Diode tube symbol", zOrder = IDIYComponent.COMPONENT,
keywordPolicy = KeywordPolicy.SHOW_VALUE, transformer = TubeSymbolTransformer.class)
public class DiodeSymbol extends AbstractTubeSymbol {
@@ -124,8 +124,8 @@ public class DiodeSymbol extends AbstractTubeSymbol {
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
- g2d.drawLine(width / 4, height / 4, width * 3 / 4, height / 4);
- g2d.drawLine(width / 2, height / 4, width / 2, 0);
+ g2d.drawLine(width / 4, height / 3, width * 3 / 4, height / 3);
+ g2d.drawLine(width / 2, height / 3, width / 2, 0);
g2d.drawLine(width / 4 + 2 * width / 32, height * 3 / 4, width * 3 / 4 - 4 * width / 32, height * 3 / 4);
g2d.drawLine(width / 4 + 2 * width / 32, height * 3 / 4, width / 4 + 2 * width / 32, height - 1);
diff --git a/diylc/diylc-library/src/org/diylc/components/tube/DuoDiodeSymbol.java b/diylc/diylc-library/src/org/diylc/components/tube/DuoDiodeSymbol.java
new file mode 100644
index 00000000..95a7e43e
--- /dev/null
+++ b/diylc/diylc-library/src/org/diylc/components/tube/DuoDiodeSymbol.java
@@ -0,0 +1,216 @@
+/*
+
+ DIY Layout Creator (DIYLC).
+ Copyright (c) 2009-2018 held jointly by the individual authors.
+
+ This file is part of DIYLC.
+
+ DIYLC is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DIYLC is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DIYLC. If not, see .
+
+*/
+package org.diylc.components.tube;
+
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+
+import org.diylc.common.ObjectCache;
+import org.diylc.components.transform.TubeSymbolTransformer;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IPropertyValidator;
+import org.diylc.core.ValidationException;
+import org.diylc.core.VisibilityPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.EditableProperty;
+import org.diylc.core.annotations.KeywordPolicy;
+
+@ComponentDescriptor(name = "Duo-Diode", author = "Branislav Stojkovic", category = "Schematic Symbols",
+ instanceNamePrefix = "V", description = "Duo-diode tube symbol", zOrder = IDIYComponent.COMPONENT,
+ keywordPolicy = KeywordPolicy.SHOW_VALUE, transformer = TubeSymbolTransformer.class)
+public class DuoDiodeSymbol extends AbstractTubeSymbol {
+
+ private static final long serialVersionUID = 1L;
+
+ protected boolean directlyHeated = false;
+
+ public DuoDiodeSymbol() {
+ super();
+ this.controlPoints =
+ new Point[] {new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)};
+ updateControlPoints();
+ }
+
+ public Shape[] initializeBody() {
+ if (body == null) {
+ Point[] controlPoints = initializeControlPoints(this.controlPoints[0]);
+
+ body = new Shape[3];
+ int x = controlPoints[0].x;
+ int y = controlPoints[0].y;
+ int pinSpacing = (int) PIN_SPACING.convertToPixels();
+
+ // electrodes
+ GeneralPath polyline = new GeneralPath();
+
+ // plate
+ polyline.moveTo(x + pinSpacing * 3 / 2, y - pinSpacing);
+ polyline.lineTo(x + pinSpacing * 5 / 2, y - pinSpacing);
+ polyline.moveTo(x + pinSpacing * 7 / 2, y - pinSpacing);
+ polyline.lineTo(x + pinSpacing * 9 / 2, y - pinSpacing);
+
+ // cathode
+ if (directlyHeated) {
+ polyline.moveTo(controlPoints[2].x, controlPoints[2].y - pinSpacing);
+ polyline.lineTo(controlPoints[2].x + pinSpacing, controlPoints[2].y - pinSpacing * 2);
+ polyline.lineTo(controlPoints[4].x, controlPoints[4].y - pinSpacing);
+ } else {
+ polyline.moveTo(x + pinSpacing * 2, y + pinSpacing);
+ polyline.lineTo(x + pinSpacing * 4, y + pinSpacing);
+ }
+
+ body[0] = polyline;
+
+ // connectors
+ polyline = new GeneralPath();
+
+ // plate
+ polyline.moveTo(controlPoints[1].x, controlPoints[1].y);
+ polyline.lineTo(controlPoints[1].x, y - pinSpacing);
+
+ polyline.moveTo(controlPoints[5].x, controlPoints[5].y);
+ polyline.lineTo(controlPoints[5].x, y - pinSpacing);
+
+ // cathode
+ if (directlyHeated) {
+ polyline.moveTo(controlPoints[2].x, controlPoints[2].y);
+ polyline.lineTo(controlPoints[2].x, controlPoints[2].y - pinSpacing);
+
+ polyline.moveTo(controlPoints[4].x, controlPoints[4].y);
+ polyline.lineTo(controlPoints[4].x, controlPoints[4].y - pinSpacing);
+ } else {
+ polyline.moveTo(controlPoints[2].x, controlPoints[2].y);
+ polyline.lineTo(x + pinSpacing * 2, y + pinSpacing);
+
+ if (showHeaters) {
+ polyline.moveTo(controlPoints[3].x, controlPoints[3].y);
+ polyline.lineTo(controlPoints[3].x, controlPoints[3].y - pinSpacing);
+ polyline.lineTo(controlPoints[3].x + pinSpacing / 2, controlPoints[3].y - 3 * pinSpacing / 2);
+
+ polyline.moveTo(controlPoints[4].x, controlPoints[4].y);
+ polyline.lineTo(controlPoints[4].x, controlPoints[4].y - pinSpacing);
+ polyline.lineTo(controlPoints[4].x - pinSpacing / 2, controlPoints[4].y - 3 * pinSpacing / 2);
+ }
+ }
+
+ body[1] = polyline;
+
+ // bulb
+ body[2] = new Ellipse2D.Double(x + pinSpacing / 2, y - pinSpacing * 5 / 2, pinSpacing * 5, pinSpacing * 5);
+ }
+ return body;
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(COLOR);
+
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
+ int plate = 5 * 32 / width;
+ g2d.drawLine(width / 4, height / 3, width / 4 + plate, height / 3);
+ g2d.drawLine(width * 3 / 4 - plate, height / 3, width * 3 / 4, height / 3);
+
+ g2d.drawLine(width / 4 + plate / 2, height / 3, width / 4 + plate / 2, 0);
+ g2d.drawLine(width * 3 / 4 - plate / 2, height / 3, width * 3 / 4 - plate / 2, 0);
+
+ g2d.drawLine(width / 4 + 2 * width / 32, height * 3 / 4, width / 4 + 2 * width / 32, height - 1);
+ g2d.drawLine(width / 4 + 2 * width / 32, height * 3 / 4, width * 3 / 4 - 4 * width / 32, height * 3 / 4);
+
+ g2d.drawOval(1, 1, width - 1 - 2 * width / 32, height - 1 - 2 * width / 32);
+ }
+
+ @Override
+ protected Point[] initializeControlPoints(Point first) {
+ int pinSpacing = (int) PIN_SPACING.convertToPixels();
+ // Update control points.
+ int x = first.x;
+ int y = first.y;
+
+ Point[] newPoints = new Point[] {first, new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)};
+
+ newPoints[1].x = x + pinSpacing * 2;
+ newPoints[1].y = y - pinSpacing * 3;
+
+ newPoints[2].x = x + pinSpacing * 2;
+ newPoints[2].y = y + pinSpacing * 3;
+
+ newPoints[3].x = x + pinSpacing * 3;
+ newPoints[3].y = y + pinSpacing * 3;
+
+ newPoints[4].x = x + pinSpacing * 4;
+ newPoints[4].y = y + pinSpacing * 3;
+
+ newPoints[5].x = x + pinSpacing * 4;
+ newPoints[5].y = y - pinSpacing * 3;
+
+ return newPoints;
+ }
+
+ @Override
+ public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
+ if (directlyHeated) {
+ return (index > 0 && index != 3) || index == 5 ? VisibilityPolicy.WHEN_SELECTED : VisibilityPolicy.NEVER;
+ } else if (showHeaters) {
+ return index > 0? VisibilityPolicy.WHEN_SELECTED : VisibilityPolicy.NEVER;
+ } else {
+ return (index < 3 && index > 0) || index == 5 ? VisibilityPolicy.WHEN_SELECTED : VisibilityPolicy.NEVER;
+ }
+ }
+
+ @Override
+ public boolean isControlPointSticky(int index) {
+ if (directlyHeated)
+ return index > 0 && index != 3;
+ else if (showHeaters) {
+ return index > 0;
+ } else {
+ return index > 0 && index < 3;
+ }
+ }
+
+ @EditableProperty(name = "Directly heated")
+ public boolean getDirectlyHeated() {
+ return directlyHeated;
+ }
+
+ public void setDirectlyHeated(boolean directlyHeated) {
+ this.directlyHeated = directlyHeated;
+
+ this.body = null;
+ }
+
+ public class HeaterValidator implements IPropertyValidator {
+
+ @Override
+ public void validate(Object owner, Object value) throws ValidationException {
+ if (value != null && value instanceof Boolean) {
+ boolean b = (Boolean) value;
+ if (!b && getDirectlyHeated()) {
+ throw new ValidationException("Must show heaters for directly heated tubes.");
+ }
+ }
+ }
+ }
+}