/*******************************************************************************
* 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.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.Graphics2D;
import java.awt.RenderingHints;
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;
class TableTab extends JPanel implements TruthTablePanel, TabInterface {
private class MyListener implements TruthTableListener {
public void cellsChanged(TruthTableEvent event) {
repaint();
}
public void structureChanged(TruthTableEvent event) {
computePreferredSize();
}
}
private static final long serialVersionUID = 1L;
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 MyListener myListener = new MyListener();
private TruthTable table;
private int cellWidth = 25; // reasonable start values
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);
}
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(Strings.get("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();
}
public void copy() {
requestFocus();
clip.copy();
}
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);
}
}
}
}
TableTabCaret getCaret() {
return caret;
}
int getCellHeight() {
return cellHeight;
}
int getCellWidth() {
return cellWidth;
}
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;
}
public int getOutputColumn(MouseEvent event) {
int inputs = table.getInputColumnCount();
if (inputs == 0)
inputs = 1;
int ret = getColumn(event);
return ret >= inputs ? ret - inputs : -1;
}
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 String getToolTipText(MouseEvent event) {
int row = getRow(event);
int col = getOutputColumn(event);
Entry entry = table.getOutputEntry(row, col);
return entry.getErrorMessage();
}
public TruthTable getTruthTable() {
return table;
}
JScrollBar getVerticalScrollBar() {
return new JScrollBar() {
private static final long serialVersionUID = 1L;
@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 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;
}
}
};
}
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;
}
void localeChanged() {
computePreferredSize();
repaint();
}
@Override
public void paintComponent(Graphics g) {
/* Anti-aliasing changes from https://github.com/hausen/logisim-evolution */
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
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, Strings.get("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(Strings.get("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(Strings.get("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);
}
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;
}
public void paste() {
requestFocus();
clip.paste();
}
public void selectAll() {
caret.selectAll();
}
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);
}
}