/*******************************************************************************
* 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.wiring;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.JDialog;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.text.MaskFormatter;
import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.circuit.RadixOption;
import com.cburch.logisim.comp.EndData;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeOption;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Attributes;
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.gui.main.Canvas;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceData;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstanceLogger;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstancePoker;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.tools.key.BitWidthConfigurator;
import com.cburch.logisim.tools.key.DirectionConfigurator;
import com.cburch.logisim.tools.key.JoinedConfigurator;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.Icons;
public class Pin extends InstanceFactory {
@SuppressWarnings("serial")
private static class EditText extends JDialog implements KeyListener {
private Value value = null;
private Value oldVal = null;
private JFormattedTextField text = null;
private int bitWidth;
RadixOption radix = RadixOption.RADIX_16;
public EditText(Value value, RadixOption radix, int width) {
super();
String mask = "";
GridBagConstraints gbc = new GridBagConstraints();
MaskFormatter formatter = new MaskFormatter();
DecimalFormat df = new DecimalFormat();
JLabel label = new JLabel("");
Color back = new Color(0xff, 0xf0, 0x99);
setUndecorated(true);
setModal(true);
setLayout(new GridBagLayout());
this.radix = radix;
bitWidth = width;
oldVal = value;
// System.err.println("Wdth:"+bitWidth);
try {
formatter.setPlaceholderCharacter('_');
if (radix == RadixOption.RADIX_16) {
label.setText("0x");
for (int i = 0; i < Math.ceil(bitWidth / 4.0); i++) {
mask += "H";
}
formatter.setMask(mask);
text = new JFormattedTextField(formatter);
text.setText(value.toHexString());
} else if (radix == RadixOption.RADIX_8) {
label.setText("0");
for (int i = 0; i < Math.ceil(bitWidth / 3.0); i++) {
mask += "#";
}
formatter.setInvalidCharacters("89");
formatter.setMask(mask);
text = new JFormattedTextField(formatter);
text.setText(value.toOctalString());
} else if (radix == RadixOption.RADIX_10_SIGNED) {
mask = "#;-#";
df.setParseIntegerOnly(true);
df.applyPattern(mask);
df.setMaximumIntegerDigits(11);
text = new JFormattedTextField(df);
text.setColumns(11);
// System.err.println("Val:" + value.toDecimalString(true));
text.setText(value.toDecimalString(true));
} else if (radix == RadixOption.RADIX_10_UNSIGNED) {
mask = "#;";
df.setParseIntegerOnly(true);
df.applyPattern(mask);
df.setMaximumIntegerDigits(10);
text = new JFormattedTextField(df);
text.setColumns(10);
// System.err.println("Val:" +
// value.toDecimalString(false));
text.setText(value.toDecimalString(false));
}
} catch (ParseException ex) {
Logger.getLogger(Pin.class.getName()).log(Level.SEVERE, null,
ex);
}
gbc.gridx = gbc.gridy = 0;
add(label, gbc);
gbc.gridx = 1;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.BASELINE;
text.addKeyListener(this);
text.setBorder(null);
text.setBackground(back);
add(text, gbc);
pack();
}
public Value getValue() {
return value;
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (text.isEditValid()) {
if (radix == RadixOption.RADIX_10_SIGNED
|| radix == RadixOption.RADIX_10_UNSIGNED) {
try {
value = Value.createKnown(
BitWidth.create(bitWidth),
(int) Long.parseLong(text.getText()));
} catch (NumberFormatException exception) {
value = oldVal;
return;
}
} else if (radix == RadixOption.RADIX_16) {
value = Value.createKnown(BitWidth.create(bitWidth),
(int) Long.parseLong(text.getText(), 16));
} else if (radix == RadixOption.RADIX_8) {
value = Value.createKnown(BitWidth.create(bitWidth),
(int) Long.parseLong(text.getText(), 8));
}
setVisible(false);
}
} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
value = oldVal;
setVisible(false);
}
}
@Override
public void keyReleased(KeyEvent e) {
;
}
@Override
public void keyTyped(KeyEvent e) {
;
}
}
public static class PinLogger extends InstanceLogger {
@Override
public String getLogName(InstanceState state, Object option) {
PinAttributes attrs = (PinAttributes) state.getAttributeSet();
String ret = attrs.label;
if (ret == null || ret.equals("")) {
String type = attrs.type == EndData.INPUT_ONLY ? Strings
.get("pinInputName") : Strings.get("pinOutputName");
return type + state.getInstance().getLocation();
} else {
return ret;
}
}
@Override
public Value getLogValue(InstanceState state, Object option) {
PinState s = getState(state);
return s.intendedValue;
}
}
public static class PinPoker extends InstancePoker {
int bitPressed = -1;
private int getBit(InstanceState state, MouseEvent e) {
BitWidth width = state.getAttributeValue(StdAttr.WIDTH);
if (width.getWidth() == 1) {
return 0;
} else {
Bounds bds = state.getInstance().getBounds(); // intentionally
// with no
// graphics
// object - we
// don't want
// label
// included
int i = (bds.getX() + bds.getWidth() - e.getX()) / 10;
int j = (bds.getY() + bds.getHeight() - e.getY()) / 20;
int bit = 8 * j + i;
if (bit < 0 || bit >= width.getWidth()) {
return -1;
} else {
return bit;
}
}
}
private void handleBitPress(InstanceState state, int bit, MouseEvent e) {
PinAttributes attrs = (PinAttributes) state.getAttributeSet();
if (!attrs.isInput()) {
return;
}
java.awt.Component sourceComp = e.getComponent();
if (sourceComp instanceof Canvas && !state.isCircuitRoot()) {
Canvas canvas = (Canvas) e.getComponent();
CircuitState circState = canvas.getCircuitState();
java.awt.Component frame = SwingUtilities.getRoot(canvas);
int choice = JOptionPane.showConfirmDialog(frame,
Strings.get("pinFrozenQuestion"),
Strings.get("pinFrozenTitle"),
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
if (choice == JOptionPane.OK_OPTION) {
circState = circState.cloneState();
canvas.getProject().setCircuitState(circState);
state = circState.getInstanceState(state.getInstance());
} else {
return;
}
}
PinState pinState = getState(state);
Value val = pinState.intendedValue.get(bit);
if (val == Value.FALSE) {
val = Value.TRUE;
} else if (val == Value.TRUE) {
val = attrs.threeState && attrs.pull == PULL_NONE ? Value.UNKNOWN
: Value.FALSE;
} else {
val = Value.FALSE;
}
pinState.intendedValue = pinState.intendedValue.set(bit, val);
state.fireInvalidated();
}
@Override
public void mousePressed(InstanceState state, MouseEvent e) {
bitPressed = getBit(state, e);
}
@Override
public void mouseReleased(InstanceState state, MouseEvent e) {
if (state.getAttributeValue(RadixOption.ATTRIBUTE) == RadixOption.RADIX_2) {
int bit = getBit(state, e);
if (bit == bitPressed && bit >= 0) {
handleBitPress(state, bit, e);
}
bitPressed = -1;
} else {
PinState pinState = getState(state);
EditText dialog = new EditText(pinState.intendedValue,
state.getAttributeValue(RadixOption.ATTRIBUTE),
pinState.intendedValue.getWidth());
dialog.setLocation(e.getXOnScreen(), e.getYOnScreen());
dialog.setVisible(true);
// System.err.println("New Value: '" + dialog.getValue() + "'");
pinState.intendedValue = dialog.getValue();
state.fireInvalidated();
}
}
}
private static class PinState implements InstanceData, Cloneable {
Value intendedValue;
Value foundValue;
public PinState(Value sending, Value receiving) {
this.intendedValue = sending;
this.foundValue = receiving;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
private static PinState getState(InstanceState state) {
PinAttributes attrs = (PinAttributes) state.getAttributeSet();
BitWidth width = attrs.width;
PinState ret = (PinState) state.getData();
if (ret == null) {
Value val = attrs.threeState ? Value.UNKNOWN : Value.FALSE;
if (width.getWidth() > 1) {
Value[] arr = new Value[width.getWidth()];
java.util.Arrays.fill(arr, val);
val = Value.create(arr);
}
ret = new PinState(val, val);
state.setData(ret);
}
if (ret.intendedValue.getWidth() != width.getWidth()) {
ret.intendedValue = ret.intendedValue.extendWidth(width.getWidth(),
attrs.threeState ? Value.UNKNOWN : Value.FALSE);
}
if (ret.foundValue.getWidth() != width.getWidth()) {
ret.foundValue = ret.foundValue.extendWidth(width.getWidth(),
Value.UNKNOWN);
}
return ret;
}
private static Value pull2(Value mod, BitWidth expectedWidth, Value pullTo) {
if (mod.getWidth() == expectedWidth.getWidth()) {
Value[] vs = mod.getAll();
for (int i = 0; i < vs.length; i++) {
if (vs[i] == Value.UNKNOWN) {
vs[i] = pullTo;
}
}
return Value.create(vs);
} else {
return Value.createKnown(expectedWidth, 0);
}
}
public static final Attribute<Boolean> ATTR_TRISTATE = Attributes
.forBoolean("tristate", Strings.getter("pinThreeStateAttr"));
public static final Attribute<Boolean> ATTR_TYPE = Attributes.forBoolean(
"output", Strings.getter("pinOutputAttr"));
public static final Attribute<Direction> ATTR_LABEL_LOC = Attributes
.forDirection("labelloc", Strings.getter("pinLabelLocAttr"));
public static final AttributeOption PULL_NONE = new AttributeOption("none",
Strings.getter("pinPullNoneOption"));
public static final AttributeOption PULL_UP = new AttributeOption("up",
Strings.getter("pinPullUpOption"));
public static final AttributeOption PULL_DOWN = new AttributeOption("down",
Strings.getter("pinPullDownOption"));
public static final Attribute<AttributeOption> ATTR_PULL = Attributes
.forOption("pull", Strings.getter("pinPullAttr"),
new AttributeOption[] { PULL_NONE, PULL_UP, PULL_DOWN });
public static final Pin FACTORY = new Pin();
private static final Icon ICON_IN = Icons.getIcon("pinInput.gif");
private static final Icon ICON_OUT = Icons.getIcon("pinOutput.gif");
private static final Font ICON_WIDTH_FONT = new Font("SansSerif",
Font.BOLD, 9);
private static final Color ICON_WIDTH_COLOR = Value.WIDTH_ERROR_COLOR
.darker();
public Pin() {
super("Pin", Strings.getter("pinComponent"));
setFacingAttribute(StdAttr.FACING);
setKeyConfigurator(JoinedConfigurator.create(new BitWidthConfigurator(
StdAttr.WIDTH), new DirectionConfigurator(ATTR_LABEL_LOC,
KeyEvent.ALT_DOWN_MASK)));
setInstanceLogger(PinLogger.class);
setInstancePoker(PinPoker.class);
}
//
// methods for instances
//
@Override
protected void configureNewInstance(Instance instance) {
PinAttributes attrs = (PinAttributes) instance.getAttributeSet();
instance.addAttributeListener();
configurePorts(instance);
Probe.configureLabel(instance, attrs.labelloc, attrs.facing);
}
private void configurePorts(Instance instance) {
PinAttributes attrs = (PinAttributes) instance.getAttributeSet();
String endType = attrs.isOutput() ? Port.INPUT : Port.OUTPUT;
Port port = new Port(0, 0, endType, StdAttr.WIDTH);
if (attrs.isOutput()) {
port.setToolTip(Strings.getter("pinOutputToolTip"));
} else {
port.setToolTip(Strings.getter("pinInputToolTip"));
}
instance.setPorts(new Port[] { port });
}
@Override
public AttributeSet createAttributeSet() {
return new PinAttributes();
}
@Override
public Bounds getOffsetBounds(AttributeSet attrs) {
Direction facing = attrs.getValue(StdAttr.FACING);
BitWidth width = attrs.getValue(StdAttr.WIDTH);
return Probe.getOffsetBounds(facing, width,
attrs.getValue(RadixOption.ATTRIBUTE) /* RadixOption.RADIX_2 */);
}
public int getType(Instance instance) {
PinAttributes attrs = (PinAttributes) instance.getAttributeSet();
return attrs.type;
}
//
// state information methods
//
public Value getValue(InstanceState state) {
return getState(state).intendedValue;
}
//
// basic information methods
//
public BitWidth getWidth(Instance instance) {
PinAttributes attrs = (PinAttributes) instance.getAttributeSet();
return attrs.width;
}
@Override
public boolean HasThreeStateDrivers(AttributeSet attrs) {
/*
* We ignore for the moment the three-state property of the pin, as it
* is not an active component, just wiring
*/
// PinAttributes myattrs = (PinAttributes) attrs;
// return myattrs.getValue(Pin.ATTR_TRISTATE);
return false;
}
@Override
public boolean HDLSupportedComponent(String HDLIdentifier,
AttributeSet attrs) {
return true;
}
@Override
protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
if (attr == ATTR_TYPE) {
configurePorts(instance);
} else if (attr == StdAttr.WIDTH || attr == StdAttr.FACING
|| attr == Pin.ATTR_LABEL_LOC || attr == RadixOption.ATTRIBUTE) {
instance.recomputeBounds();
PinAttributes attrs = (PinAttributes) instance.getAttributeSet();
Probe.configureLabel(instance, attrs.labelloc, attrs.facing);
} else if (attr == Pin.ATTR_TRISTATE || attr == Pin.ATTR_PULL) {
instance.fireInvalidated();
}
}
public boolean isInputPin(Instance instance) {
PinAttributes attrs = (PinAttributes) instance.getAttributeSet();
return attrs.type != EndData.OUTPUT_ONLY;
}
@Override
public void paintGhost(InstancePainter painter) {
PinAttributes attrs = (PinAttributes) painter.getAttributeSet();
Location loc = painter.getLocation();
Bounds bds = painter.getOffsetBounds();
int x = loc.getX();
int y = loc.getY();
Graphics g = painter.getGraphics();
GraphicsUtil.switchToWidth(g, 2);
boolean output = attrs.isOutput();
if (output) {
BitWidth width = attrs.getValue(StdAttr.WIDTH);
if (width == BitWidth.ONE) {
g.drawOval(x + bds.getX() + 1, y + bds.getY() + 1,
bds.getWidth() - 1, bds.getHeight() - 1);
} else {
g.drawRoundRect(x + bds.getX() + 1, y + bds.getY() + 1,
bds.getWidth() - 1, bds.getHeight() - 1, 6, 6);
}
} else {
g.drawRect(x + bds.getX() + 1, y + bds.getY() + 1,
bds.getWidth() - 1, bds.getHeight() - 1);
}
}
//
// graphics methods
//
@Override
public void paintIcon(InstancePainter painter) {
paintIconBase(painter);
BitWidth w = painter.getAttributeValue(StdAttr.WIDTH);
if (!w.equals(BitWidth.ONE)) {
Graphics g = painter.getGraphics();
g.setColor(ICON_WIDTH_COLOR);
g.setFont(ICON_WIDTH_FONT);
GraphicsUtil.drawCenteredText(g, "" + w.getWidth(), 10, 9);
g.setColor(Color.BLACK);
}
}
private void paintIconBase(InstancePainter painter) {
PinAttributes attrs = (PinAttributes) painter.getAttributeSet();
Direction dir = attrs.facing;
boolean output = attrs.isOutput();
Graphics g = painter.getGraphics();
if (output) {
if (ICON_OUT != null) {
Icons.paintRotated(g, 2, 2, dir, ICON_OUT,
painter.getDestination());
return;
}
} else {
if (ICON_IN != null) {
Icons.paintRotated(g, 2, 2, dir, ICON_IN,
painter.getDestination());
return;
}
}
int pinx = 16;
int piny = 9;
if (dir == Direction.EAST) { // keep defaults
} else if (dir == Direction.WEST) {
pinx = 4;
} else if (dir == Direction.NORTH) {
pinx = 9;
piny = 4;
} else if (dir == Direction.SOUTH) {
pinx = 9;
piny = 16;
}
g.setColor(Color.black);
if (output) {
g.drawOval(4, 4, 13, 13);
} else {
g.drawRect(4, 4, 13, 13);
}
g.setColor(Value.TRUE.getColor());
g.fillOval(7, 7, 8, 8);
g.fillOval(pinx, piny, 3, 3);
}
@Override
public void paintInstance(InstancePainter painter) {
PinAttributes attrs = (PinAttributes) painter.getAttributeSet();
Graphics g = painter.getGraphics();
Bounds bds = painter.getInstance().getBounds(); // intentionally with no
// graphics object - we
// don't want label
// included
int x = bds.getX();
int y = bds.getY();
GraphicsUtil.switchToWidth(g, 2);
g.setColor(Color.black);
if (attrs.type == EndData.OUTPUT_ONLY) {
if (attrs.width.getWidth() == 1) {
g.drawOval(x + 1, y + 1, bds.getWidth() - 1,
bds.getHeight() - 1);
} else {
g.drawRoundRect(x + 1, y + 1, bds.getWidth() - 1,
bds.getHeight() - 1, 12, 12);
}
} else {
g.drawRect(x + 1, y + 1, bds.getWidth() - 1, bds.getHeight() - 1);
}
painter.drawLabel();
if (!painter.getShowState()) {
g.setColor(Color.BLACK);
GraphicsUtil.drawCenteredText(g, "x" + attrs.width.getWidth(),
bds.getX() + bds.getWidth() / 2,
bds.getY() + bds.getHeight() / 2);
} else {
PinState state = getState(painter);
if (attrs.width.getWidth() <= 1) {
Value found = state.foundValue;
g.setColor(found.getColor());
g.fillOval(x + 4, y + 4, 13, 13);
if (attrs.width.getWidth() == 1) {
g.setColor(Color.WHITE);
GraphicsUtil.drawCenteredText(g,
state.intendedValue.toDisplayString(), x + 11,
y + 9);
}
} else {
Probe.paintValue(painter, state.intendedValue);
}
}
painter.drawPorts();
}
@Override
public void propagate(InstanceState state) {
PinAttributes attrs = (PinAttributes) state.getAttributeSet();
PinState q = getState(state);
if (attrs.type == EndData.OUTPUT_ONLY) {
Value found = state.getPortValue(0);
q.intendedValue = found;
q.foundValue = found;
state.setPort(0, Value.createUnknown(attrs.width), 1);
} else {
Value found = state.getPortValue(0);
Value toSend = q.intendedValue;
Object pull = attrs.pull;
Value pullTo = null;
if (pull == PULL_DOWN) {
pullTo = Value.FALSE;
} else if (pull == PULL_UP) {
pullTo = Value.TRUE;
} else if (!attrs.threeState && !state.isCircuitRoot()) {
pullTo = Value.FALSE;
}
if (pullTo != null) {
toSend = pull2(toSend, attrs.width, pullTo);
if (state.isCircuitRoot()) {
q.intendedValue = toSend;
}
}
q.foundValue = found;
if (!toSend.equals(found)) { // ignore if no change
state.setPort(0, toSend, 1);
}
}
}
@Override
public boolean RequiresNonZeroLabel() {
return true;
}
public void setValue(InstanceState state, Value value) {
PinAttributes attrs = (PinAttributes) state.getAttributeSet();
Object pull = attrs.pull;
PinState myState = getState(state);
if (value == Value.NIL) {
myState.intendedValue = Value.createUnknown(attrs.width);
} else {
Value sendValue;
if (pull == PULL_NONE || pull == null || value.isFullyDefined()) {
sendValue = value;
} else {
Value[] bits = value.getAll();
if (pull == PULL_UP) {
for (int i = 0; i < bits.length; i++) {
if (bits[i] != Value.FALSE)
bits[i] = Value.TRUE;
}
} else if (pull == PULL_DOWN) {
for (int i = 0; i < bits.length; i++) {
if (bits[i] != Value.TRUE)
bits[i] = Value.FALSE;
}
}
sendValue = Value.create(bits);
}
myState.intendedValue = sendValue;
}
}
}