/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
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.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.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 {
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)));
}
@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 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);
}
}
@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;
}
}
//
// painting methods
//
@Override
public void paintGhost(InstancePainter painter) {
paintBase(painter);
}
@Override
public void paintInstance(InstancePainter painter) {
paintBase(painter);
if (!painter.isPrintView() || painter.getGateShape() == AppPreferences.SHAPE_RECTANGULAR) {
painter.drawPorts();
}
}
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 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;
}
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 getIconShaped() {
return getIcon(0);
}
private Icon getIconRectangular() {
return getIcon(1);
}
private Icon getIconDin40700() {
return getIcon(2);
}
protected void setPaintInputLines(boolean value) {
paintInputLines = value;
}
protected abstract void paintIconShaped(InstancePainter 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);
}
@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 setAdditionalWidth(int value) {
bonusWidth = value;
}
protected void setNegateOutput(boolean value) {
negateOutput = value;
}
protected void setRectangularLabel(String value) {
rectLabel = value;
}
protected String getRectangularLabel(AttributeSet attrs) {
return rectLabel;
}
//
// protected methods intended to be overridden
//
protected abstract Value getIdentity();
protected abstract void paintShape(InstancePainter painter,
int width, int height);
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 paintDinShape(InstancePainter painter,
int width, int height, int inputs);
protected abstract Value computeOutput(Value[] inputs, int numInputs,
InstanceState state);
protected abstract Expression computeExpression(Expression[] inputs,
int numInputs);
protected boolean shouldRepairWire(Instance instance, WireRepairData data) {
return false;
}
//
// methods for instances
//
@Override
protected void configureNewInstance(Instance instance) {
instance.addAttributeListener();
computePorts(instance);
computeLabel(instance);
}
@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 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);
}
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);
}
@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.getPort(i).not();
} else {
inputs[numInputs] = state.getPort(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);
}
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);
}
}
@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);
}
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);
}
}
}