/* 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.memory;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeOption;
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.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.util.GraphicsUtil;
import com.cburch.logisim.util.StringGetter;
abstract class AbstractFlipFlop extends InstanceFactory {
private static final int STD_PORTS = 6;
private Attribute<AttributeOption> triggerAttribute;
protected AbstractFlipFlop(String name, String iconName, StringGetter desc,
int numInputs, boolean allowLevelTriggers) {
super(name, desc);
setIconName(iconName);
triggerAttribute = allowLevelTriggers ? StdAttr.TRIGGER : StdAttr.EDGE_TRIGGER;
setAttributes(new Attribute[] {
triggerAttribute, StdAttr.LABEL, StdAttr.LABEL_FONT
}, new Object[] {
StdAttr.TRIG_RISING, "", StdAttr.DEFAULT_LABEL_FONT
});
setOffsetBounds(Bounds.create(-40, -10, 40, 40));
setInstancePoker(Poker.class);
setInstanceLogger(Logger.class);
Port[] ps = new Port[numInputs + STD_PORTS];
if (numInputs == 1) {
ps[0] = new Port(-40, 20, Port.INPUT, 1);
ps[1] = new Port(-40, 0, Port.INPUT, 1);
} else if (numInputs == 2) {
ps[0] = new Port(-40, 0, Port.INPUT, 1);
ps[1] = new Port(-40, 20, Port.INPUT, 1);
ps[2] = new Port(-40, 10, Port.INPUT, 1);
} else {
throw new RuntimeException("flip-flop input > 2");
}
ps[numInputs + 1] = new Port( 0, 0, Port.OUTPUT, 1);
ps[numInputs + 2] = new Port( 0, 20, Port.OUTPUT, 1);
ps[numInputs + 3] = new Port(-10, 30, Port.INPUT, 1);
ps[numInputs + 4] = new Port(-30, 30, Port.INPUT, 1);
ps[numInputs + 5] = new Port(-20, 30, Port.INPUT, 1);
ps[numInputs].setToolTip(Strings.getter("flipFlopClockTip"));
ps[numInputs + 1].setToolTip(Strings.getter("flipFlopQTip"));
ps[numInputs + 2].setToolTip(Strings.getter("flipFlopNotQTip"));
ps[numInputs + 3].setToolTip(Strings.getter("flipFlopResetTip"));
ps[numInputs + 4].setToolTip(Strings.getter("flipFlopPresetTip"));
ps[numInputs + 5].setToolTip(Strings.getter("flipFlopEnableTip"));
setPorts(ps);
}
//
// abstract methods intended to be implemented in subclasses
//
protected abstract String getInputName(int index);
protected abstract Value computeValue(Value[] inputs,
Value curValue);
//
// concrete methods not intended to be overridden
//
@Override
protected void configureNewInstance(Instance instance) {
Bounds bds = instance.getBounds();
instance.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT,
bds.getX() + bds.getWidth() / 2, bds.getY() - 3,
GraphicsUtil.H_CENTER, GraphicsUtil.V_BASELINE);
}
@Override
public void propagate(InstanceState state) {
boolean changed = false;
StateData data = (StateData) state.getData();
if (data == null) {
changed = true;
data = new StateData();
state.setData(data);
}
int n = getPorts().size() - STD_PORTS;
Object triggerType = state.getAttributeValue(triggerAttribute);
boolean triggered = data.updateClock(state.getPort(n), triggerType);
if (state.getPort(n + 3) == Value.TRUE) { // clear requested
changed |= data.curValue != Value.FALSE;
data.curValue = Value.FALSE;
} else if (state.getPort(n + 4) == Value.TRUE) { // preset requested
changed |= data.curValue != Value.TRUE;
data.curValue = Value.TRUE;
} else if (triggered && state.getPort(n + 5) != Value.FALSE) {
// Clock has triggered and flip-flop is enabled: Update the state
Value[] inputs = new Value[n];
for (int i = 0; i < n; i++) {
inputs[i] = state.getPort(i);
}
Value newVal = computeValue(inputs, data.curValue);
if (newVal == Value.TRUE || newVal == Value.FALSE) {
changed |= data.curValue != newVal;
data.curValue = newVal;
}
}
state.setPort(n + 1, data.curValue, Memory.DELAY);
state.setPort(n + 2, data.curValue.not(), Memory.DELAY);
}
@Override
public void paintInstance(InstancePainter painter) {
Graphics g = painter.getGraphics();
painter.drawBounds();
painter.drawLabel();
if (painter.getShowState()) {
Location loc = painter.getLocation();
StateData myState = (StateData) painter.getData();
if (myState != null) {
int x = loc.getX();
int y = loc.getY();
g.setColor(myState.curValue.getColor());
g.fillOval(x - 26, y + 4, 13, 13);
g.setColor(Color.WHITE);
GraphicsUtil.drawCenteredText(g,
myState.curValue.toDisplayString(), x - 19, y + 9);
g.setColor(Color.BLACK);
}
}
int n = getPorts().size() - STD_PORTS;
g.setColor(Color.GRAY);
painter.drawPort(n + 3, "0", Direction.SOUTH);
painter.drawPort(n + 4, "1", Direction.SOUTH);
painter.drawPort(n + 5, Strings.get("memEnableLabel"), Direction.SOUTH);
g.setColor(Color.BLACK);
for (int i = 0; i < n; i++) {
painter.drawPort(i, getInputName(i), Direction.EAST);
}
painter.drawClock(n, Direction.EAST);
painter.drawPort(n + 1, "Q", Direction.WEST);
painter.drawPort(n + 2);
}
private static class StateData extends ClockState implements InstanceData {
Value curValue = Value.FALSE;
}
public static class Logger extends InstanceLogger {
@Override
public String getLogName(InstanceState state, Object option) {
String ret = state.getAttributeValue(StdAttr.LABEL);
return ret != null && !ret.equals("") ? ret : null;
}
@Override
public Value getLogValue(InstanceState state, Object option) {
StateData s = (StateData) state.getData();
return s == null ? Value.FALSE : s.curValue;
}
}
public static class Poker extends InstancePoker {
boolean isPressed = true;
@Override
public void mousePressed(InstanceState state, MouseEvent e) {
isPressed = isInside(state, e);
}
@Override
public void mouseReleased(InstanceState state, MouseEvent e) {
if (isPressed && isInside(state, e)) {
StateData myState = (StateData) state.getData();
if (myState == null) return;
myState.curValue = myState.curValue.not();
state.fireInvalidated();
}
isPressed = false;
}
private boolean isInside(InstanceState state, MouseEvent e) {
Location loc = state.getInstance().getLocation();
int dx = e.getX() - (loc.getX() - 20);
int dy = e.getY() - (loc.getY() + 10);
int d2 = dx * dx + dy * dy;
return d2 < 8 * 8;
}
}
}