/* 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.analyze.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import com.cburch.logisim.analyze.model.Entry;
import com.cburch.logisim.analyze.model.TruthTable;
import com.cburch.logisim.analyze.model.TruthTableEvent;
import com.cburch.logisim.analyze.model.TruthTableListener;
import com.cburch.logisim.util.GraphicsUtil;
import static com.cburch.logisim.util.LocaleString.getFromLocale;
@SuppressWarnings("serial")
class TableTab extends JPanel implements TruthTablePanel, TabInterface {
private static final Font HEAD_FONT = new Font("Serif", Font.BOLD, 14);
private static final Font BODY_FONT = new Font("Serif", Font.PLAIN, 14);
private static final int COLUMN_SEP = 8;
private static final int HEADER_SEP = 4;
private class MyListener implements TruthTableListener {
@Override
public void cellsChanged(TruthTableEvent event) {
repaint();
}
@Override
public void structureChanged(TruthTableEvent event) {
computePreferredSize();
}
}
private MyListener myListener = new MyListener();
private TruthTable table;
// reasonable start values
private int cellWidth = 25;
private int cellHeight = 15;
private int tableWidth;
private int tableHeight;
private int provisionalX;
private int provisionalY;
private Entry provisionalValue = null;
private TableTabCaret caret;
private TableTabClip clip;
public TableTab(TruthTable table) {
this.table = table;
table.addTruthTableListener(myListener);
setToolTipText(" ");
caret = new TableTabCaret(this);
clip = new TableTabClip(this);
}
@Override
public TruthTable getTruthTable() {
return table;
}
TableTabCaret getCaret() {
return caret;
}
void localeChanged() {
computePreferredSize();
repaint();
}
public int getColumn(MouseEvent event) {
int x = event.getX() - (getWidth() - tableWidth) / 2;
if (x < 0) return -1;
int inputs = table.getInputColumnCount();
int cols = inputs + table.getOutputColumnCount();
int ret = (x + COLUMN_SEP / 2) / (cellWidth + COLUMN_SEP);
if (inputs == 0) ret--;
return ret >= 0 ? ret < cols ? ret : cols : -1;
}
int getColumnCount() {
int inputs = table.getInputColumnCount();
int outputs = table.getOutputColumnCount();
return inputs + outputs;
}
@Override
public int getOutputColumn(MouseEvent event) {
int inputs = table.getInputColumnCount();
if (inputs == 0) inputs = 1;
int ret = getColumn(event);
return ret >= inputs ? ret - inputs : -1;
}
@Override
public int getRow(MouseEvent event) {
int y = event.getY() - (getHeight() - tableHeight) / 2;
if (y < cellHeight + HEADER_SEP) return -1;
int ret = (y - cellHeight - HEADER_SEP) / cellHeight;
int rows = table.getRowCount();
return ret >= 0 ? ret < rows ? ret : rows : -1;
}
@Override
public void setEntryProvisional(int y, int x, Entry value) {
provisionalY = y;
provisionalX = x;
provisionalValue = value;
int top = (getHeight() - tableHeight) / 2 + cellHeight + HEADER_SEP
+ y * cellHeight;
repaint(0, top, getWidth(), cellHeight);
}
@Override
public String getToolTipText(MouseEvent event) {
int row = getRow(event);
int col = getOutputColumn(event);
Entry entry = table.getOutputEntry(row, col);
return entry.getErrorMessage();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
caret.paintBackground(g);
Dimension sz = getSize();
int top = Math.max(0, (sz.height - tableHeight) / 2);
int left = Math.max(0, (sz.width - tableWidth) / 2);
int inputs = table.getInputColumnCount();
int outputs = table.getOutputColumnCount();
if (inputs == 0 && outputs == 0) {
g.setFont(BODY_FONT);
GraphicsUtil.drawCenteredText(g, getFromLocale("tableEmptyMessage"), sz.width / 2, sz.height / 2);
return;
}
g.setColor(Color.GRAY);
int lineX = left + (cellWidth + COLUMN_SEP) * inputs
- COLUMN_SEP / 2;
if (inputs == 0) lineX = left + cellWidth + COLUMN_SEP / 2;
int lineY = top + cellHeight + HEADER_SEP / 2;
g.drawLine(left, lineY, left + tableWidth, lineY);
g.drawLine(lineX, top, lineX, top + tableHeight);
g.setColor(Color.BLACK);
g.setFont(HEAD_FONT);
FontMetrics headerMetric = g.getFontMetrics();
int x = left;
int y = top + headerMetric.getAscent() + 1;
if (inputs == 0) {
x = paintHeader(getFromLocale("tableNullHeader"), x, y, g, headerMetric);
} else {
for (int i = 0; i < inputs; i++) {
x = paintHeader(table.getInputHeader(i), x, y, g, headerMetric);
}
}
if (outputs == 0) {
x = paintHeader(getFromLocale("tableNullHeader"), x, y, g, headerMetric);
} else {
for (int i = 0; i < outputs; i++) {
x = paintHeader(table.getOutputHeader(i), x, y, g, headerMetric);
}
}
g.setFont(BODY_FONT);
FontMetrics bodyMetric = g.getFontMetrics();
y = top + cellHeight + HEADER_SEP;
Rectangle clip = g.getClipBounds();
int firstRow = Math.max(0, (clip.y - y) / cellHeight);
int lastRow = Math.min(table.getRowCount(), 2 + (clip.y + clip.height - y) / cellHeight);
y += firstRow * cellHeight;
if (inputs == 0) left += cellWidth + COLUMN_SEP;
boolean provisional = false;
for (int i = firstRow; i < lastRow; i++) {
x = left;
for (int j = 0; j < inputs + outputs; j++) {
Entry entry = j < inputs ? table.getInputEntry(i, j)
: table.getOutputEntry(i, j - inputs);
if (provisionalValue != null && i == provisionalY
&& j - inputs == provisionalX) {
provisional = true;
entry = provisionalValue;
}
if (entry.isError()) {
g.setColor(ERROR_COLOR);
g.fillRect(x, y, cellWidth, cellHeight);
g.setColor(Color.BLACK);
}
String label = entry.getDescription();
int width = bodyMetric.stringWidth(label);
if (provisional) {
provisional = false;
g.setColor(Color.GREEN);
g.drawString(label, x + (cellWidth - width) / 2,
y + bodyMetric.getAscent());
g.setColor(Color.BLACK);
} else {
g.drawString(label, x + (cellWidth - width) / 2,
y + bodyMetric.getAscent());
}
x += cellWidth + COLUMN_SEP;
}
y += cellHeight;
}
caret.paintForeground(g);
}
int getCellWidth() { return cellWidth; }
int getCellHeight() { return cellHeight; }
int getX(int col) {
Dimension sz = getSize();
int left = Math.max(0, (sz.width - tableWidth) / 2);
int inputs = table.getInputColumnCount();
if (inputs == 0) left += cellWidth + COLUMN_SEP;
return left + col * (cellWidth + COLUMN_SEP);
}
int getY(int row) {
Dimension sz = getSize();
int top = Math.max(0, (sz.height - tableHeight) / 2);
return top + cellHeight + HEADER_SEP + row * cellHeight;
}
private int paintHeader(String header, int x, int y,
Graphics g, FontMetrics fm) {
int width = fm.stringWidth(header);
g.drawString(header, x + (cellWidth - width) / 2, y);
return x + cellWidth + COLUMN_SEP;
}
private void computePreferredSize() {
int inputs = table.getInputColumnCount();
int outputs = table.getOutputColumnCount();
if (inputs == 0 && outputs == 0) {
setPreferredSize(new Dimension(0, 0));
return;
}
Graphics g = getGraphics();
if (g == null) {
cellHeight = 16;
cellWidth = 24;
} else {
FontMetrics fm = g.getFontMetrics(HEAD_FONT);
cellHeight = fm.getHeight();
cellWidth = 24;
if (inputs == 0 || outputs == 0) {
cellWidth = Math.max(cellWidth, fm.stringWidth(getFromLocale("tableNullHeader")));
}
for (int i = 0; i < inputs + outputs; i++) {
String header = i < inputs ? table.getInputHeader(i)
: table.getOutputHeader(i - inputs);
cellWidth = Math.max(cellWidth, fm.stringWidth(header));
}
}
if (inputs == 0) inputs = 1;
if (outputs == 0) outputs = 1;
tableWidth = (cellWidth + COLUMN_SEP) * (inputs + outputs) - COLUMN_SEP;
tableHeight = cellHeight * (1 + table.getRowCount()) + HEADER_SEP;
setPreferredSize(new Dimension(tableWidth, tableHeight));
revalidate();
repaint();
}
JScrollBar getVerticalScrollBar() {
return new JScrollBar() {
@Override
public int getUnitIncrement(int direction) {
int curY = getValue();
if (direction > 0) {
return curY > 0 ? cellHeight : cellHeight + HEADER_SEP;
} else {
return curY > cellHeight + HEADER_SEP ? cellHeight
: cellHeight + HEADER_SEP;
}
}
@Override
public int getBlockIncrement(int direction) {
int curY = getValue();
int curHeight = getVisibleAmount();
int numCells = curHeight / cellHeight - 1;
if (numCells <= 0) numCells = 1;
if (direction > 0) {
return curY > 0
? numCells * cellHeight
: numCells * cellHeight + HEADER_SEP;
} else {
return curY > cellHeight + HEADER_SEP
? numCells * cellHeight
: numCells * cellHeight + HEADER_SEP;
}
}
};
}
@Override
public void copy() {
requestFocus();
clip.copy();
}
@Override
public void paste() {
requestFocus();
clip.paste();
}
@Override
public void delete() {
requestFocus();
int r0 = caret.getCursorRow();
int r1 = caret.getMarkRow();
int c0 = caret.getCursorCol();
int c1 = caret.getMarkCol();
if (r0 < 0 || r1 < 0) return;
if (r1 < r0) { int t = r0; r0 = r1; r1 = t; }
if (c1 < c0) { int t = c0; c0 = c1; c1 = t; }
int inputs = table.getInputColumnCount();
for (int c = c0; c <= c1; c++) {
if (c >= inputs) {
for (int r = r0; r <= r1; r++) {
table.setOutputEntry(r, c - inputs, Entry.DONT_CARE);
}
}
}
}
@Override
public void selectAll() {
caret.selectAll();
}
}