/* 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.io;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Arrays;
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.Bounds;
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.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.std.wiring.DurationAttribute;
import com.cburch.logisim.util.GraphicsUtil;
// TODO repropagate when rows/cols change
public class DotMatrix extends InstanceFactory {
static final AttributeOption INPUT_SELECT
= new AttributeOption("select", Strings.getter("ioInputSelect"));
static final AttributeOption INPUT_COLUMN
= new AttributeOption("column", Strings.getter("ioInputColumn"));
static final AttributeOption INPUT_ROW
= new AttributeOption("row", Strings.getter("ioInputRow"));
static final AttributeOption SHAPE_CIRCLE
= new AttributeOption("circle", Strings.getter("ioShapeCircle"));
static final AttributeOption SHAPE_SQUARE
= new AttributeOption("square", Strings.getter("ioShapeSquare"));
static final Attribute<AttributeOption> ATTR_INPUT_TYPE
= Attributes.forOption("inputtype", Strings.getter("ioMatrixInput"),
new AttributeOption[] { INPUT_COLUMN, INPUT_ROW, INPUT_SELECT });
static final Attribute<Integer> ATTR_MATRIX_COLS
= Attributes.forIntegerRange("matrixcols",
Strings.getter("ioMatrixCols"), 1, Value.MAX_WIDTH);
static final Attribute<Integer> ATTR_MATRIX_ROWS
= Attributes.forIntegerRange("matrixrows",
Strings.getter("ioMatrixRows"), 1, Value.MAX_WIDTH);
static final Attribute<AttributeOption> ATTR_DOT_SHAPE
= Attributes.forOption("dotshape", Strings.getter("ioMatrixShape"),
new AttributeOption[] { SHAPE_CIRCLE, SHAPE_SQUARE });
static final Attribute<Integer> ATTR_PERSIST = new DurationAttribute("persist",
Strings.getter("ioMatrixPersistenceAttr"), 0, Integer.MAX_VALUE);
public DotMatrix() {
super("DotMatrix", Strings.getter("dotMatrixComponent"));
setAttributes(new Attribute<?>[] {
ATTR_INPUT_TYPE, ATTR_MATRIX_COLS, ATTR_MATRIX_ROWS,
Io.ATTR_ON_COLOR, Io.ATTR_OFF_COLOR,
ATTR_PERSIST, ATTR_DOT_SHAPE
}, new Object[] {
INPUT_COLUMN, Integer.valueOf(5), Integer.valueOf(7),
Color.GREEN, Color.DARK_GRAY, Integer.valueOf(0), SHAPE_SQUARE
});
setIconName("dotmat.gif");
}
@Override
public Bounds getOffsetBounds(AttributeSet attrs) {
Object input = attrs.getValue(ATTR_INPUT_TYPE);
int cols = attrs.getValue(ATTR_MATRIX_COLS).intValue();
int rows = attrs.getValue(ATTR_MATRIX_ROWS).intValue();
if (input == INPUT_COLUMN) {
return Bounds.create(-5, -10 * rows, 10 * cols, 10 * rows);
} else if (input == INPUT_ROW) {
return Bounds.create(0, -5, 10 * cols, 10 * rows);
} else { // input == INPUT_SELECT
if (rows == 1) {
return Bounds.create(0, -5, 10 * cols, 10 * rows);
} else {
return Bounds.create(0, -5 * rows + 5, 10 * cols, 10 * rows);
}
}
}
@Override
protected void configureNewInstance(Instance instance) {
instance.addAttributeListener();
updatePorts(instance);
}
@Override
protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
if (attr == ATTR_MATRIX_ROWS || attr == ATTR_MATRIX_COLS
|| attr == ATTR_INPUT_TYPE) {
instance.recomputeBounds();
updatePorts(instance);
}
}
private void updatePorts(Instance instance) {
Object input = instance.getAttributeValue(ATTR_INPUT_TYPE);
int rows = instance.getAttributeValue(ATTR_MATRIX_ROWS).intValue();
int cols = instance.getAttributeValue(ATTR_MATRIX_COLS).intValue();
Port[] ps;
if (input == INPUT_COLUMN) {
ps = new Port[cols];
for (int i = 0; i < cols; i++) {
ps[i] = new Port(10 * i, 0, Port.INPUT, rows);
}
} else if (input == INPUT_ROW) {
ps = new Port[rows];
for (int i = 0; i < rows; i++) {
ps[i] = new Port(0, 10 * i, Port.INPUT, cols);
}
} else {
if (rows <= 1) {
ps = new Port[] { new Port(0, 0, Port.INPUT, cols) };
} else if (cols <= 1) {
ps = new Port[] { new Port(0, 0, Port.INPUT, rows) };
} else {
ps = new Port[] {
new Port(0, 0, Port.INPUT, cols),
new Port(0, 10, Port.INPUT, rows)
};
}
}
instance.setPorts(ps);
}
private State getState(InstanceState state) {
int rows = state.getAttributeValue(ATTR_MATRIX_ROWS).intValue();
int cols = state.getAttributeValue(ATTR_MATRIX_COLS).intValue();
long clock = state.getTickCount();
State data = (State) state.getData();
if (data == null) {
data = new State(rows, cols, clock);
state.setData(data);
} else {
data.updateSize(rows, cols, clock);
}
return data;
}
@Override
public void propagate(InstanceState state) {
Object type = state.getAttributeValue(ATTR_INPUT_TYPE);
int rows = state.getAttributeValue(ATTR_MATRIX_ROWS).intValue();
int cols = state.getAttributeValue(ATTR_MATRIX_COLS).intValue();
long clock = state.getTickCount();
long persist = clock + state.getAttributeValue(ATTR_PERSIST).intValue();
State data = getState(state);
if (type == INPUT_ROW) {
for (int i = 0; i < rows; i++) {
data.setRow(i, state.getPort(i), persist);
}
} else if (type == INPUT_COLUMN) {
for (int i = 0; i < cols; i++) {
data.setColumn(i, state.getPort(i), persist);
}
} else if (type == INPUT_SELECT) {
data.setSelect(state.getPort(1), state.getPort(0), persist);
} else {
throw new RuntimeException("unexpected matrix type");
}
}
@Override
public void paintInstance(InstancePainter painter) {
Color onColor = painter.getAttributeValue(Io.ATTR_ON_COLOR);
Color offColor = painter.getAttributeValue(Io.ATTR_OFF_COLOR);
boolean drawSquare = painter.getAttributeValue(ATTR_DOT_SHAPE) == SHAPE_SQUARE;
State data = getState(painter);
long ticks = painter.getTickCount();
Bounds bds = painter.getBounds();
boolean showState = painter.getShowState();
Graphics g = painter.getGraphics();
int rows = data.rows;
int cols = data.cols;
for (int j = 0; j < rows; j++) {
for (int i = 0; i < cols; i++) {
int x = bds.getX() + 10 * i;
int y = bds.getY() + 10 * j;
if (showState) {
Value val = data.get(j, i, ticks);
Color c;
if (val == Value.TRUE) c = onColor;
else if (val == Value.FALSE) c = offColor;
else c = Value.ERROR_COLOR;
g.setColor(c);
if (drawSquare) g.fillRect(x, y, 10, 10);
else g.fillOval(x + 1, y + 1, 8, 8);
} else {
g.setColor(Color.GRAY);
g.fillOval(x + 1, y + 1, 8, 8);
}
}
}
g.setColor(Color.BLACK);
GraphicsUtil.switchToWidth(g, 2);
g.drawRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight());
GraphicsUtil.switchToWidth(g, 1);
painter.drawPorts();
}
private static class State implements InstanceData, Cloneable {
private int rows;
private int cols;
private Value[] grid;
private long[] persistTo;
public State(int rows, int cols, long curClock) {
this.rows = -1;
this.cols = -1;
updateSize(rows, cols, curClock);
}
@Override
public Object clone() {
try {
State ret = (State) super.clone();
ret.grid = this.grid.clone();
ret.persistTo = this.persistTo.clone();
return ret;
} catch (CloneNotSupportedException e) {
return null;
}
}
private void updateSize(int rows, int cols, long curClock) {
if (this.rows != rows || this.cols != cols) {
this.rows = rows;
this.cols = cols;
int length = rows * cols;
grid = new Value[length];
persistTo = new long[length];
Arrays.fill(grid, Value.UNKNOWN);
Arrays.fill(persistTo, curClock - 1);
}
}
private Value get(int row, int col, long curTick) {
int index = row * cols + col;
Value ret = grid[index];
if (ret == Value.FALSE && persistTo[index] - curTick >= 0) {
ret = Value.TRUE;
}
return ret;
}
private void setRow(int index, Value rowVector, long persist) {
int gridloc = (index + 1) * cols - 1;
int stride = -1;
Value[] vals = rowVector.getAll();
for (int i = 0; i < vals.length; i++, gridloc += stride) {
Value val = vals[i];
if (grid[gridloc] == Value.TRUE) {
persistTo[gridloc] = persist - 1;
}
grid[gridloc] = vals[i];
if (val == Value.TRUE) {
persistTo[gridloc] = persist;
}
}
}
private void setColumn(int index, Value colVector, long persist) {
int gridloc = (rows - 1) * cols + index;
int stride = -cols;
Value[] vals = colVector.getAll();
for (int i = 0; i < vals.length; i++, gridloc += stride) {
Value val = vals[i];
if (grid[gridloc] == Value.TRUE) {
persistTo[gridloc] = persist - 1;
}
grid[gridloc] = val;
if (val == Value.TRUE) {
persistTo[gridloc] = persist;
}
}
}
private void setSelect(Value rowVector, Value colVector, long persist) {
Value[] rowVals = rowVector.getAll();
Value[] colVals = colVector.getAll();
int gridloc = 0;
for (int i = rowVals.length - 1; i >= 0; i--) {
Value wholeRow = rowVals[i];
if (wholeRow == Value.TRUE) {
for (int j = colVals.length - 1; j >= 0; j--, gridloc++) {
Value val = colVals[colVals.length - 1 - j];
if (grid[gridloc] == Value.TRUE) {
persistTo[gridloc] = persist - 1;
}
grid[gridloc] = val;
if (val == Value.TRUE) {
persistTo[gridloc] = persist;
}
}
} else {
if (wholeRow != Value.FALSE) wholeRow = Value.ERROR;
for (int j = colVals.length - 1; j >= 0; j--, gridloc++) {
if (grid[gridloc] == Value.TRUE) {
persistTo[gridloc] = persist - 1;
}
grid[gridloc] = wholeRow;
}
}
}
}
}
}