/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution 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.
*
* logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.std.gates;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Map;
import javax.swing.Icon;
import com.bfh.logisim.designrulecheck.CorrectLabel;
import com.cburch.logisim.LogisimVersion;
import com.cburch.logisim.analyze.model.Expression;
import com.cburch.logisim.analyze.model.Expressions;
import com.cburch.logisim.circuit.ExpressionComputer;
import com.cburch.logisim.comp.TextField;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.file.Options;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.tools.WireRepair;
import com.cburch.logisim.tools.WireRepairData;
import com.cburch.logisim.tools.key.BitWidthConfigurator;
import com.cburch.logisim.tools.key.IntegerConfigurator;
import com.cburch.logisim.tools.key.JoinedConfigurator;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.Icons;
import com.cburch.logisim.util.StringGetter;
abstract class AbstractGate extends InstanceFactory {
static Value pullOutput(Value value, Object outType) {
if (outType == GateAttributes.OUTPUT_01) {
return value;
} else {
Value[] v = value.getAll();
if (outType == GateAttributes.OUTPUT_0Z) {
for (int i = 0; i < v.length; i++) {
if (v[i] == Value.TRUE)
v[i] = Value.UNKNOWN;
}
} else if (outType == GateAttributes.OUTPUT_Z1) {
for (int i = 0; i < v.length; i++) {
if (v[i] == Value.FALSE)
v[i] = Value.UNKNOWN;
}
}
return Value.create(v);
}
}
private String[] iconNames = new String[3];
private Icon[] icons = new Icon[3];
private int bonusWidth = 0;
private boolean negateOutput = false;
private boolean isXor = false;
private String rectLabel = "";
private boolean paintInputLines;
protected AbstractGate(String name, StringGetter desc) {
this(name, desc, false);
}
protected AbstractGate(String name, StringGetter desc, boolean isXor) {
super(name, desc);
this.isXor = isXor;
setFacingAttribute(StdAttr.FACING);
setKeyConfigurator(JoinedConfigurator.create(new IntegerConfigurator(
GateAttributes.ATTR_INPUTS, 2, GateAttributes.MAX_INPUTS, 0),
new BitWidthConfigurator(StdAttr.WIDTH)));
}
protected abstract Expression computeExpression(Expression[] inputs,
int numInputs);
private void computeLabel(Instance instance) {
GateAttributes attrs = (GateAttributes) instance.getAttributeSet();
Direction facing = attrs.facing;
int baseWidth = ((Integer) attrs.size.getValue()).intValue();
int axis = baseWidth / 2 + (negateOutput ? 10 : 0);
int perp = 0;
if (AppPreferences.GATE_SHAPE.get().equals(
AppPreferences.SHAPE_RECTANGULAR)) {
perp += 6;
}
Location loc = instance.getLocation();
int cx;
int cy;
if (facing == Direction.NORTH) {
cx = loc.getX() + perp;
cy = loc.getY() + axis;
} else if (facing == Direction.SOUTH) {
cx = loc.getX() - perp;
cy = loc.getY() - axis;
} else if (facing == Direction.WEST) {
cx = loc.getX() + axis;
cy = loc.getY() - perp;
} else {
cx = loc.getX() - axis;
cy = loc.getY() + perp;
}
instance.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT, cx, cy,
TextField.H_CENTER, TextField.V_CENTER);
}
protected abstract Value computeOutput(Value[] inputs, int numInputs,
InstanceState state);
void computePorts(Instance instance) {
GateAttributes attrs = (GateAttributes) instance.getAttributeSet();
int inputs = attrs.inputs;
Port[] ports = new Port[inputs + 1];
ports[0] = new Port(0, 0, Port.OUTPUT, StdAttr.WIDTH);
for (int i = 0; i < inputs; i++) {
Location offs = getInputOffset(attrs, i);
ports[i + 1] = new Port(offs.getX(), offs.getY(), Port.INPUT,
StdAttr.WIDTH);
}
instance.setPorts(ports);
}
//
// methods for instances
//
@Override
protected void configureNewInstance(Instance instance) {
instance.addAttributeListener();
computePorts(instance);
computeLabel(instance);
}
@Override
public boolean contains(Location loc, AttributeSet attrsBase) {
GateAttributes attrs = (GateAttributes) attrsBase;
if (super.contains(loc, attrs)) {
if (attrs.negated == 0) {
return true;
} else {
Direction facing = attrs.facing;
Bounds bds = getOffsetBounds(attrsBase);
int delt;
if (facing == Direction.NORTH) {
delt = loc.getY() - (bds.getY() + bds.getHeight());
} else if (facing == Direction.SOUTH) {
delt = loc.getY() - bds.getY();
} else if (facing == Direction.WEST) {
delt = loc.getX() - (bds.getX() + bds.getHeight());
} else {
delt = loc.getX() - bds.getX();
}
if (Math.abs(delt) > 5) {
return true;
} else {
int inputs = attrs.inputs;
for (int i = 1; i <= inputs; i++) {
Location offs = getInputOffset(attrs, i);
if (loc.manhattanDistanceTo(offs) <= 5)
return true;
}
return false;
}
}
} else {
return false;
}
}
@Override
public AttributeSet createAttributeSet() {
return new GateAttributes(isXor);
}
@Override
public Object getDefaultAttributeValue(Attribute<?> attr, LogisimVersion ver) {
if (attr instanceof NegateAttribute) {
return Boolean.FALSE;
} else {
return super.getDefaultAttributeValue(attr, ver);
}
}
@Override
public String getHDLName(AttributeSet attrs) {
GateAttributes myattrs = (GateAttributes) attrs;
StringBuffer CompleteName = new StringBuffer();
CompleteName.append(CorrectLabel.getCorrectLabel(this.getName())
.toUpperCase());
BitWidth width = myattrs.getValue(StdAttr.WIDTH);
if (width.getWidth() > 1)
CompleteName.append("_BUS");
Integer inputCount = myattrs.getValue(GateAttributes.ATTR_INPUTS);
if (inputCount > 2) {
CompleteName.append("_" + inputCount.toString() + "_INPUTS");
}
if (myattrs.containsAttribute(GateAttributes.ATTR_XOR)) {
if (myattrs.getValue(GateAttributes.ATTR_XOR).equals(
GateAttributes.XOR_ONE)) {
CompleteName.append("_ONEHOT");
}
}
return CompleteName.toString();
}
private Icon getIcon(int type) {
Icon ret = icons[type];
if (ret != null) {
return ret;
} else {
String iconName = iconNames[type];
if (iconName == null) {
return null;
} else {
ret = Icons.getIcon(iconName);
if (ret == null) {
iconNames[type] = null;
} else {
icons[type] = ret;
}
return ret;
}
}
}
private Icon getIconDin40700() {
return getIcon(2);
}
private Icon getIconRectangular() {
return getIcon(1);
}
private Icon getIconShaped() {
return getIcon(0);
}
//
// protected methods intended to be overridden
//
protected abstract Value getIdentity();
Location getInputOffset(GateAttributes attrs, int index) {
int inputs = attrs.inputs;
Direction facing = attrs.facing;
int size = ((Integer) attrs.size.getValue()).intValue();
int axisLength = size + bonusWidth + (negateOutput ? 10 : 0);
int negated = attrs.negated;
int skipStart;
int skipDist;
int skipLowerEven = 10;
if (inputs <= 3) {
if (size < 40) {
skipStart = -5;
skipDist = 10;
skipLowerEven = 10;
} else if (size < 60 || inputs <= 2) {
skipStart = -10;
skipDist = 20;
skipLowerEven = 20;
} else {
skipStart = -15;
skipDist = 30;
skipLowerEven = 30;
}
} else if (inputs == 4 && size >= 60) {
skipStart = -5;
skipDist = 20;
skipLowerEven = 0;
} else {
skipStart = -5;
skipDist = 10;
skipLowerEven = 10;
}
int dy;
if ((inputs & 1) == 1) {
dy = skipStart * (inputs - 1) + skipDist * index;
} else {
dy = skipStart * inputs + skipDist * index;
if (index >= inputs / 2)
dy += skipLowerEven;
}
int dx = axisLength;
int negatedBit = (negated >> index) & 1;
if (negatedBit == 1) {
dx += 10;
}
if (facing == Direction.NORTH) {
return Location.create(dy, dx);
} else if (facing == Direction.SOUTH) {
return Location.create(dy, -dx);
} else if (facing == Direction.WEST) {
return Location.create(dx, dy);
} else {
return Location.create(-dx, dy);
}
}
@Override
protected Object getInstanceFeature(final Instance instance, Object key) {
if (key == WireRepair.class) {
return new WireRepair() {
public boolean shouldRepairWire(WireRepairData data) {
return AbstractGate.this.shouldRepairWire(instance, data);
}
};
}
if (key == ExpressionComputer.class) {
return new ExpressionComputer() {
public void computeExpression(
Map<Location, Expression> expressionMap) {
GateAttributes attrs = (GateAttributes) instance
.getAttributeSet();
int inputCount = attrs.inputs;
int negated = attrs.negated;
Expression[] inputs = new Expression[inputCount];
int numInputs = 0;
for (int i = 1; i <= inputCount; i++) {
Expression e = expressionMap.get(instance
.getPortLocation(i));
if (e != null) {
int negatedBit = (negated >> (i - 1)) & 1;
if (negatedBit == 1) {
e = Expressions.not(e);
}
inputs[numInputs] = e;
++numInputs;
}
}
if (numInputs > 0) {
Expression out = AbstractGate.this.computeExpression(
inputs, numInputs);
expressionMap.put(instance.getPortLocation(0), out);
}
}
};
}
return super.getInstanceFeature(instance, key);
}
@Override
public Bounds getOffsetBounds(AttributeSet attrsBase) {
GateAttributes attrs = (GateAttributes) attrsBase;
Direction facing = attrs.facing;
int size = ((Integer) attrs.size.getValue()).intValue();
int inputs = attrs.inputs;
if (inputs % 2 == 0) {
inputs++;
}
int negated = attrs.negated;
int width = size + bonusWidth + (negateOutput ? 10 : 0);
if (negated != 0) {
width += 10;
}
int height = Math.max(10 * inputs, size);
if (facing == Direction.SOUTH) {
return Bounds.create(-height / 2, -width, height, width);
} else if (facing == Direction.NORTH) {
return Bounds.create(-height / 2, 0, height, width);
} else if (facing == Direction.WEST) {
return Bounds.create(0, -height / 2, width, height);
} else {
return Bounds.create(-width, -height / 2, width, height);
}
}
protected String getRectangularLabel(AttributeSet attrs) {
return rectLabel;
}
@Override
public boolean HasThreeStateDrivers(AttributeSet attrs) {
if (attrs.containsAttribute(GateAttributes.ATTR_OUTPUT))
return !(attrs.getValue(GateAttributes.ATTR_OUTPUT) == GateAttributes.OUTPUT_01);
else
return false;
}
@Override
protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
if (attr == GateAttributes.ATTR_SIZE || attr == StdAttr.FACING) {
instance.recomputeBounds();
computePorts(instance);
computeLabel(instance);
} else if (attr == GateAttributes.ATTR_INPUTS
|| attr instanceof NegateAttribute) {
instance.recomputeBounds();
computePorts(instance);
} else if (attr == GateAttributes.ATTR_XOR) {
instance.fireInvalidated();
}
}
private void paintBase(InstancePainter painter) {
GateAttributes attrs = (GateAttributes) painter.getAttributeSet();
Direction facing = attrs.facing;
int inputs = attrs.inputs;
int negated = attrs.negated;
Object shape = painter.getGateShape();
Location loc = painter.getLocation();
Bounds bds = painter.getOffsetBounds();
int width = bds.getWidth();
int height = bds.getHeight();
if (facing == Direction.NORTH || facing == Direction.SOUTH) {
int t = width;
width = height;
height = t;
}
if (negated != 0) {
width -= 10;
}
Graphics g = painter.getGraphics();
Color baseColor = g.getColor();
if (shape == AppPreferences.SHAPE_SHAPED && paintInputLines) {
PainterShaped.paintInputLines(painter, this);
} else if (negated != 0) {
for (int i = 0; i < inputs; i++) {
int negatedBit = (negated >> i) & 1;
if (negatedBit == 1) {
Location in = getInputOffset(attrs, i);
Location cen = in.translate(facing, 5);
painter.drawDongle(loc.getX() + cen.getX(), loc.getY()
+ cen.getY());
}
}
}
g.setColor(baseColor);
g.translate(loc.getX(), loc.getY());
double rotate = 0.0;
if (facing != Direction.EAST && g instanceof Graphics2D) {
rotate = -facing.toRadians();
Graphics2D g2 = (Graphics2D) g;
g2.rotate(rotate);
}
if (shape == AppPreferences.SHAPE_RECTANGULAR) {
paintRectangular(painter, width, height);
} else if (shape == AppPreferences.SHAPE_DIN40700) {
paintDinShape(painter, width, height, inputs);
} else { // SHAPE_SHAPED
if (negateOutput) {
g.translate(-10, 0);
paintShape(painter, width - 10, height);
painter.drawDongle(5, 0);
g.translate(10, 0);
} else {
paintShape(painter, width, height);
}
}
if (rotate != 0.0) {
((Graphics2D) g).rotate(-rotate);
}
g.translate(-loc.getX(), -loc.getY());
painter.drawLabel();
}
protected abstract void paintDinShape(InstancePainter painter, int width,
int height, int inputs);
//
// painting methods
//
@Override
public void paintGhost(InstancePainter painter) {
paintBase(painter);
}
@Override
public final void paintIcon(InstancePainter painter) {
Graphics g = painter.getGraphics();
g.setColor(Color.black);
if (painter.getGateShape() == AppPreferences.SHAPE_RECTANGULAR) {
Icon iconRect = getIconRectangular();
if (iconRect != null) {
iconRect.paintIcon(painter.getDestination(), g, 2, 2);
} else {
paintIconRectangular(painter);
}
} else if (painter.getGateShape() == AppPreferences.SHAPE_DIN40700) {
Icon iconDin = getIconDin40700();
if (iconDin != null) {
iconDin.paintIcon(painter.getDestination(), g, 2, 2);
} else {
paintIconRectangular(painter);
}
} else {
Icon iconShaped = getIconShaped();
if (iconShaped != null) {
iconShaped.paintIcon(painter.getDestination(), g, 2, 2);
} else {
paintIconShaped(painter);
}
}
}
protected void paintIconRectangular(InstancePainter painter) {
Graphics g = painter.getGraphics();
g.drawRect(1, 2, 16, 16);
if (negateOutput)
g.drawOval(16, 8, 4, 4);
String label = getRectangularLabel(painter.getAttributeSet());
GraphicsUtil.drawCenteredText(g, label, 9, 8);
}
protected abstract void paintIconShaped(InstancePainter painter);
@Override
public void paintInstance(InstancePainter painter) {
paintBase(painter);
if (!painter.isPrintView()
|| painter.getGateShape() == AppPreferences.SHAPE_RECTANGULAR) {
painter.drawPorts();
}
}
protected void paintRectangular(InstancePainter painter, int width,
int height) {
int don = negateOutput ? 10 : 0;
AttributeSet attrs = painter.getAttributeSet();
painter.drawRectangle(-width, -height / 2, width - don, height,
getRectangularLabel(attrs));
if (negateOutput) {
painter.drawDongle(-5, 0);
}
}
protected abstract void paintShape(InstancePainter painter, int width,
int height);
@Override
public void propagate(InstanceState state) {
GateAttributes attrs = (GateAttributes) state.getAttributeSet();
int inputCount = attrs.inputs;
int negated = attrs.negated;
AttributeSet opts = state.getProject().getOptions().getAttributeSet();
boolean errorIfUndefined = opts.getValue(Options.ATTR_GATE_UNDEFINED)
.equals(Options.GATE_UNDEFINED_ERROR);
Value[] inputs = new Value[inputCount];
int numInputs = 0;
boolean error = false;
for (int i = 1; i <= inputCount; i++) {
if (state.isPortConnected(i)) {
int negatedBit = (negated >> (i - 1)) & 1;
if (negatedBit == 1) {
inputs[numInputs] = state.getPortValue(i).not();
} else {
inputs[numInputs] = state.getPortValue(i);
}
numInputs++;
} else {
if (errorIfUndefined) {
error = true;
}
}
}
Value out = null;
if (numInputs == 0 || error) {
out = Value.createError(attrs.width);
} else {
out = computeOutput(inputs, numInputs, state);
out = pullOutput(out, attrs.out);
}
state.setPort(0, out, GateAttributes.DELAY);
}
protected void setAdditionalWidth(int value) {
bonusWidth = value;
}
protected void setIconNames(String all) {
setIconNames(all, all, all);
}
protected void setIconNames(String shaped, String rect, String din) {
iconNames[0] = shaped;
iconNames[1] = rect;
iconNames[2] = din;
}
protected void setNegateOutput(boolean value) {
negateOutput = value;
}
protected void setPaintInputLines(boolean value) {
paintInputLines = value;
}
protected void setRectangularLabel(String value) {
rectLabel = value;
}
protected boolean shouldRepairWire(Instance instance, WireRepairData data) {
return false;
}
}