/*******************************************************************************
* 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.gui.log;
/**
* Code taken from Cornell's version of Logisim:
* http://www.cs.cornell.edu/courses/cs3410/2015sp/
*/
import java.awt.BorderLayout;
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 javax.swing.JScrollPane;
import javax.swing.ToolTipManager;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.StringUtil;
public class ValueTable extends JPanel {
public static class Cell {
public Object value;
public Color bg, fg;
public String tip;
public Cell(Object v, Color b, Color f, String t) {
value = v;
bg = b;
fg = f;
tip = t;
}
}
public static interface Model {
void changeColumnValueRadix(int i);
int getColumnCount();
String getColumnName(int i);
int getColumnValueRadix(int i);
BitWidth getColumnValueWidth(int i);
int getRowCount();
void getRowData(int firstRow, int rowCount, Cell[][] rowData);
}
private class TableBody extends JPanel {
private static final long serialVersionUID = 1L;
public String getToolTipText(MouseEvent event) {
int col = model == null ? -1 : findColumn(event.getX(),
getSize().width);
if (col < 0)
return null;
int row = rowData == null ? -1 : findRow(event.getY(),
getSize().height);
if (!(rowStart <= row && row < rowStart + rowCount))
return null;
Cell cell = rowData[row - rowStart][col];
if (cell == null)
return null;
return cell.tip;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension sz = getSize();
g.setColor(Color.BLACK);
g.setFont(BODY_FONT);
int columns = model == null ? 0 : model.getColumnCount();
if (columns == 0) {
rowCount = 0;
GraphicsUtil.drawCenteredText(g,
Strings.get("tableEmptyMessage"), sz.width / 2,
sz.height / 2);
return;
}
FontMetrics bodyMetric = g.getFontMetrics();
Rectangle clip = g.getClipBounds();
refreshData(clip.y, clip.y + clip.height);
if (rowCount == 0)
return;
int firstRow = Math.max(0, clip.y / cellHeight);
int lastRow = Math.min(model.getRowCount() - 1,
(clip.y + clip.height) / cellHeight);
int top = 0;
int left = Math.max(0, (sz.width - tableWidth) / 2);
int x = left + COLUMN_SEP;
Color bg = getBackground();
for (int col = 0; col < columns; col++) {
int y = top + firstRow * cellHeight;
g.setColor(Color.GRAY);
g.drawLine(x - COLUMN_SEP / 2, clip.y, x - COLUMN_SEP / 2,
clip.y + clip.height);
g.setColor(Color.BLACK);
int cellWidth = columnWidth[col];
int radix = model.getColumnValueRadix(col);
for (int row = firstRow; row <= lastRow; row++) {
if (!(rowStart <= row && row < rowStart + rowCount))
continue;
Cell cell = rowData[row - rowStart][col];
if (cell == null)
continue;
g.setColor(cell.bg == null ? bg : cell.bg);
g.fillRect(x - COLUMN_SEP / 2 + 1, y, cellWidth
+ COLUMN_SEP - 1, cellHeight);
g.setColor(Color.BLACK);
if (cell.value != null) {
String label = (cell.value instanceof Value ? ((Value) cell.value)
.toDisplayString(radix) : (String) cell.value);
int width = bodyMetric.stringWidth(label);
if (cell.fg != null)
g.setColor(cell.fg);
g.drawString(label, x + (cellWidth - width) / 2, y
+ bodyMetric.getAscent());
if (cell.fg != null)
g.setColor(Color.BLACK);
}
y += cellHeight;
}
x += cellWidth + COLUMN_SEP;
}
g.setColor(Color.GRAY);
g.drawLine(x - COLUMN_SEP / 2, clip.y, x - COLUMN_SEP / 2, clip.y
+ clip.height);
}
}
private class TableHeader extends JPanel {
class MyListener extends java.awt.event.MouseAdapter {
public void mouseClicked(MouseEvent e) {
int col = model == null ? -1 : findColumn(e.getX(),
getSize().width);
if (col >= 0)
model.changeColumnValueRadix(col);
}
}
private static final long serialVersionUID = 1L;
TableHeader() {
addMouseListener(new MyListener());
}
public String getToolTipText(MouseEvent event) {
int col = model == null ? -1 : findColumn(event.getX(),
getSize().width);
if (col < 0)
return null;
int radix = model.getColumnValueRadix(col);
if (radix == 0)
return null;
return StringUtil.format(Strings.get("tableHeaderHelp"),
Integer.toString(radix));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension sz = getSize();
g.setColor(Color.GRAY);
int columns = model == null ? 0 : model.getColumnCount();
if (columns == 0) {
g.drawLine(0, cellHeight + HEADER_SEP / 2, sz.width, cellHeight
+ HEADER_SEP / 2);
return;
}
g.setFont(HEAD_FONT);
FontMetrics headerMetric = g.getFontMetrics();
int top = 0;
int left = Math.max(0, (sz.width - tableWidth) / 2);
g.drawLine(left, cellHeight + HEADER_SEP / 2, left + tableWidth,
cellHeight + HEADER_SEP / 2);
int x = left + COLUMN_SEP;
int y = top + headerMetric.getAscent() + 1;
for (int i = 0; i < columns; i++) {
g.setColor(Color.GRAY);
g.drawLine(x - COLUMN_SEP / 2, 0, x - COLUMN_SEP / 2,
cellHeight);
g.setColor(Color.BLACK);
String label = model.getColumnName(i);
int cellWidth = columnWidth[i];
int width = headerMetric.stringWidth(label);
g.drawString(label, x + (cellWidth - width) / 2, y);
x += cellWidth + COLUMN_SEP;
}
g.setColor(Color.GRAY);
g.drawLine(x - COLUMN_SEP / 2, 0, x - COLUMN_SEP / 2, cellHeight);
}
}
private class VerticalScrollBar extends JScrollBar implements
ChangeListener {
private static final long serialVersionUID = 1L;
private int oldMaximum = -1;
private int oldExtent = -1;
public VerticalScrollBar() {
getModel().addChangeListener(this);
}
public int getBlockIncrement(int direction) {
int curHeight = getVisibleAmount();
int numCells = curHeight / cellHeight - 1;
if (numCells <= 0)
numCells = 1;
return numCells * cellHeight;
}
public int getUnitIncrement(int direction) {
return cellHeight;
}
public void stateChanged(ChangeEvent event) {
int newMaximum = getMaximum();
int newExtent = getVisibleAmount();
if (oldMaximum != newMaximum || oldExtent != newExtent) {
if (getValue() + oldExtent >= oldMaximum) {
setValue(newMaximum - newExtent);
}
oldMaximum = newMaximum;
oldExtent = newExtent;
}
}
}
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("Monospaced", Font.PLAIN, 14);
private static final int COLUMN_SEP = 8;
private static final int HEADER_SEP = 4;
// cached copy of rows that are visible
private Cell[][] rowData;
private int rowStart;
private int rowCount;
private int columnWidth[];
private int cellHeight;
private int tableWidth;
private int tableHeight;
private TableHeader header;
private TableBody body;
private VerticalScrollBar vsb;
private JScrollPane scrollPane;
private Model model;
public ValueTable(Model model) {
header = new TableHeader();
body = new TableBody();
vsb = new VerticalScrollBar();
scrollPane = new JScrollPane(body,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBar(vsb);
scrollPane.setColumnHeaderView(header);
scrollPane.getViewport().setBorder(null);
Border b = scrollPane.getViewportBorder();
scrollPane.setViewportBorder(null);
scrollPane.setBorder(b);
setLayout(new BorderLayout());
add(scrollPane);
ToolTipManager.sharedInstance().registerComponent(header);
ToolTipManager.sharedInstance().registerComponent(body);
setModel(model);
}
private void computePreferredSize() {
int oldCellHeight = cellHeight;
int oldTableWidth = tableWidth;
int oldTableHeight = tableHeight;
int columns = model == null ? 0 : model.getColumnCount();
if (columnWidth == null || columnWidth.length < columns)
columnWidth = new int[columns];
if (columns == 0) {
cellHeight = 16;
tableWidth = tableHeight = 0;
} else {
Graphics g = getGraphics();
int cellsWidth = 0;
if (g == null) {
cellHeight = 16;
cellsWidth = 24 * columns;
} else {
FontMetrics headerMetric = g.getFontMetrics(HEAD_FONT);
FontMetrics bodyMetric = g.getFontMetrics(BODY_FONT);
cellHeight = Math.max(headerMetric.getHeight(),
bodyMetric.getHeight());
for (int i = 0; i < columns; i++) {
int radix = model.getColumnValueRadix(i);
// column should be at least as wide as 24, as header, and
// as formatted value
String header = model.getColumnName(i);
int cellWidth = Math.max(24,
headerMetric.stringWidth(header));
BitWidth w = model.getColumnValueWidth(i);
if (w != null) {
Value val = Value.createKnown(
w,
(radix == 2 ? 0 : (radix == 10 ? (1 << (w
.getWidth() - 1)) : w.getMask())));
String label = val.toDisplayString(radix);
cellWidth = Math.max(cellWidth,
bodyMetric.stringWidth(label));
}
columnWidth[i] = cellWidth;
cellsWidth += cellWidth;
}
}
tableWidth = cellsWidth + COLUMN_SEP * (columns + 1);
tableHeight = cellHeight * model.getRowCount();
}
if (cellHeight != oldCellHeight || tableWidth != oldTableWidth
|| tableHeight != oldTableHeight) {
Dimension headSize = new Dimension(tableWidth, cellHeight
+ HEADER_SEP);
Dimension bodySize = new Dimension(tableWidth, tableHeight);
body.setPreferredSize(bodySize);
header.setPreferredSize(headSize);
body.revalidate();
header.revalidate();
}
}
public void dataChanged() {
rowCount = 0;
repaint();
}
int findColumn(int x, int width) {
int left = Math.max(0, (width - tableWidth) / 2);
if (x < left + COLUMN_SEP || x >= left + tableWidth)
return -1;
left += COLUMN_SEP;
int columns = model.getColumnCount();
for (int i = 0; i < columns; i++) {
int cellWidth = columnWidth[i];
if (x >= left && x < left + cellWidth)
return i;
left += cellWidth + COLUMN_SEP;
}
return -1;
}
int findRow(int y, int height) {
if (y < 0)
return -1;
int row = y / cellHeight;
if (row >= rowCount)
return -1;
return row;
}
public void modelChanged() {
computePreferredSize();
dataChanged();
}
void refreshData(int top, int bottom) {
int columns = model == null ? 0 : model.getColumnCount();
if (columns == 0) {
rowCount = 0;
return;
}
int rows = model.getRowCount();
if (rows == 0) {
rowCount = 0;
return;
}
int toprow = Math.min(rows - 1, Math.max(0, top / cellHeight));
int bottomrow = Math.min(rows - 1, Math.max(0, bottom / cellHeight));
if (rowData != null && rowStart <= toprow
&& toprow < rowStart + rowCount && rowStart <= bottomrow
&& bottomrow < rowStart + rowCount)
return;
// we pre-fetch a bit more than strictly visible
Rectangle rect = scrollPane.getViewport().getViewRect();
top = rect.y - rect.height / 2;
bottom = rect.y + rect.height * 2;
toprow = Math.min(rows - 1, Math.max(0, top / cellHeight - 10));
bottomrow = Math.min(rows - 1, Math.max(0, bottom / cellHeight + 10));
rowStart = Math.min(toprow, bottomrow);
rowCount = Math.max(toprow, bottomrow) - rowStart + 1;
if (rowCount == 0)
return;
if (rowData == null || rowData.length < rowCount
|| rowData[0].length != columns)
rowData = new Cell[rowCount + 1][columns];
model.getRowData(rowStart, rowCount, rowData);
}
public void setModel(Model model) {
this.model = model;
modelChanged();
}
}