/*
* Open-Source tuning tools
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.vgi.mafscaling;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.border.LineBorder;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.table.DefaultTableModel;
import org.apache.log4j.Logger;
public class LogPlayTable extends JFrame implements ActionListener {
private static final long serialVersionUID = 3534186983281827915L;
private static final Logger logger = Logger.getLogger(LogPlayTable.class);
class LinePoint {
public double x = 0;
public double y = 0;
public LinePoint(double x, double y) { this.x = x; this.y = y; }
}
private static final String SaveDataFileHeader = "[log_play_table data]";
private static final int ColumnWidth = 50;
private static final int RowColumnCount = 2;
private JFileChooser fileChooser = null;
private Window parent = null;
private ExcelAdapter excelAdapter = null;
private JTextField xText = null;
private JTextField yText = null;
private JTextField zText = null;
private JTextField valueText = null;
private JButton loadButton = null;
private JButton saveButton = null;
private JTable playTable = null;
private JPanel glasspanel = null;
private JPanel dataPanel = null;
private volatile boolean showInterpolationCells = false;
private volatile boolean showSiginficantCell = false;
private volatile boolean showTraceLine = false;
private ArrayList<Double> xaxis = new ArrayList<Double>();
private ArrayList<Double> yaxis = new ArrayList<Double>();
private Color highlight = new Color(255, 0, 255, 128);
private Color traceHighlight = new Color(44, 53, 57);
private Color cellHighlight = new Color(96, 96, 96, 128);
private Color borderHighlight = new Color(255, 239, 213);
private Insets insetsText = new Insets(1, 5, 5, 3);
private Insets insetsLabel = new Insets(5, 5, 1, 3);
private Object lock = new Object();
private ArrayList<LinePoint> tracePoints = new ArrayList<LinePoint>();
private ArrayList<LinePoint> linePoints = null;
private Ellipse2D.Double pointCircle = new Ellipse2D.Double();
private Rectangle interpRectangle = new Rectangle();
private Rectangle signifRectangle;
private Double xVal = null;
private Double yVal = null;
private Double zVal = null;
private LinePoint pt = null;
private double diameter;
private double cellWidth;
private double radius;
private double x, y, z, px, py, xPos, yPos, val, xCellVal, yCellVal;
private double x0, x1, y0, y1, x0y0, x1y0, x0y1, x1y1;
private int xIdx, yIdx, x0Idx, x1Idx, y0Idx, y1Idx, xMult, yMult;
public LogPlayTable(Window owner, String tableName) {
super(tableName);
parent = owner;
final JFrame frame = this;
excelAdapter = new ExcelAdapter() {
protected void onPaste(JTable table, boolean extendRows, boolean extendCols) {
super.onPaste(table, extendRows, extendCols);
validateTable(table);
frame.pack();
}
};
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
try {
px = py = 0;
createFileChooser();
createGlassPanel();
createDataPanel();
pack();
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setIconImage((new ImageIcon(getClass().getResource("/table.jpg"))).getImage());
setResizable(false);
setLocationRelativeTo(parent);
setVisible(true);
}
catch (Exception e) {
logger.error(e);
}
}
private boolean validateTable(JTable table) {
if (!Utils.validateTable(table))
return false;
xaxis.clear();
for (int i = 1; i < table.getColumnCount(); ++i)
xaxis.add(Double.valueOf(table.getValueAt(0, i).toString()));
yaxis.clear();
for (int i = 1; i < table.getRowCount(); ++i)
yaxis.add(Double.valueOf(table.getValueAt(i, 0).toString()));
diameter = table.getCellRect(0, 0, false).getHeight();
radius = diameter / 2.0;
cellWidth = table.getCellRect(0, 0, false).getWidth();
return true;
}
public void setEditable(boolean flag) {
playTable.setEnabled(flag);
playTable.setFocusable(flag);
loadButton.setEnabled(flag);
saveButton.setEnabled(flag);
if (flag)
excelAdapter.addTable(playTable, true, true, false, false, true, false, true, true, true);
else {
playTable.clearSelection();
excelAdapter.removeTable(playTable);
}
}
public void setShowInterpolationCells(boolean flag) {
showInterpolationCells = flag;
}
public void setShowSignificantCell(boolean flag) {
showSiginficantCell = flag;
}
public void setShowTraceLine(boolean flag) {
showTraceLine = flag;
if (!flag) {
synchronized (lock) {
tracePoints.clear();
}
}
}
public void setCurrentPoint(double x, double y, double z) {
synchronized (lock) {
xVal = x;
yVal = y;
zVal = z;
if (showTraceLine && pt != null)
tracePoints.add(pt);
}
glasspanel.repaint();
}
private void createFileChooser() {
File appdir = new File(".");
FileSystemView fsv = new RestrictedFileSystemView(appdir);
fileChooser = new JFileChooser(fsv);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setAcceptAllFileFilterUsed(false);
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("PlayTable file (.play)", "play"));
}
private void createGlassPanel() {
glasspanel = new JPanel() {
private static final long serialVersionUID = -9053819700839609869L;
public void paintComponent(Graphics g) {
try {
if (xaxis.size() > 0 && yaxis.size() > 0 && xVal != null && yVal != null) {
synchronized (lock) {
x = xVal;
y = yVal;
z = zVal;
linePoints = new ArrayList<LinePoint>(tracePoints);
}
if (px == x && py == y) {
// draw trace line
if (showTraceLine && linePoints.size() > 1) {
g.setColor(traceHighlight);
int sz = linePoints.size() - 1;
for (int i = 0; i < sz; ++i)
g.drawLine((int)linePoints.get(i).x, (int)linePoints.get(i).y, (int)linePoints.get(i + 1).x, (int)linePoints.get(i + 1).y);
}
// mark cells used in interpolation
if (showInterpolationCells) {
g.setColor(cellHighlight);
g.fillRect(interpRectangle.x, interpRectangle.y, interpRectangle.width, interpRectangle.height);
g.setColor(Color.BLACK);
g.drawRect(interpRectangle.x, interpRectangle.y, interpRectangle.width, interpRectangle.height);
}
// mark most significant cell
Graphics2D g2 = (Graphics2D)g;
if (showSiginficantCell) {
g2.setColor(borderHighlight);
g2.draw(signifRectangle);
}
// draw current value point
g2.setColor(highlight);
g2.fill(pointCircle);
g2.setColor(Color.BLACK);
g2.draw(pointCircle);
}
else {
px = x;
py = y;
xIdx = Utils.closestValueIndex(x, xaxis);
yIdx = Utils.closestValueIndex(y, yaxis);
signifRectangle = playTable.getCellRect(yIdx + 1, xIdx + 1, false);
xCellVal = xaxis.get(xIdx);
xMult = 1;
if (x < xCellVal) {
x1Idx = xIdx;
x1 = xCellVal;
if (xIdx > 0) {
xPos = playTable.getCellRect(yIdx + 1, xIdx, false).getX();
xMult = 2;
x0Idx = xIdx - 1;
x0 = xaxis.get(x0Idx);
}
else {
xPos = signifRectangle.getX();
x0Idx = x1Idx;
x0 = x1;
}
}
else if (x > xCellVal) {
x0Idx = xIdx;
x0 = xCellVal;
xPos = signifRectangle.getX();
if (xIdx < xaxis.size() - 1) {
xMult = 2;
x1Idx = xIdx + 1;
x1 = xaxis.get(x1Idx);
}
else {
x1Idx = x0Idx;
x1 = x0;
}
}
else {
x0Idx = x1Idx = xIdx;
x0 = x1 = xCellVal;
xPos = signifRectangle.getX();
}
yMult = 1;
yCellVal = yaxis.get(yIdx);
if (y < yCellVal) {
y1Idx = yIdx;
y1 = yCellVal;
if (yIdx > 0) {
yPos = playTable.getCellRect(yIdx, xIdx + 1, false).getY();
yMult = 2;
y0Idx = yIdx - 1;
y0 = yaxis.get(y0Idx);
}
else {
yPos = signifRectangle.getY();
y0Idx = y1Idx;
y0 = y1;
}
}
else if (y > yCellVal) {
y0Idx = yIdx;
y0 = yCellVal;
yPos = signifRectangle.getY();
if (yIdx < yaxis.size() - 1) {
yMult = 2;
y1Idx = yIdx + 1;
y1 = yaxis.get(y1Idx);
}
else {
y1Idx = y0Idx;
y1 = y0;
}
}
else {
y0Idx = y1Idx = yIdx;
y0 = y1 = yCellVal;
yPos = signifRectangle.getY();
}
x0y0 = Double.valueOf(playTable.getValueAt(y0Idx + 1, x0Idx + 1).toString());
x1y0 = Double.valueOf(playTable.getValueAt(y0Idx + 1, x1Idx + 1).toString());
x0y1 = Double.valueOf(playTable.getValueAt(y1Idx + 1, x0Idx + 1).toString());
x1y1 = Double.valueOf(playTable.getValueAt(y1Idx + 1, x1Idx + 1).toString());
val = Utils.bilinearInterpolation(x, y, x0, x1, y0, y1, x0y0, x0y1, x1y0, x1y1);
xText.setText(String.format("%.2f", x));
yText.setText(String.format("%.2f", y));
valueText.setText(String.format("%.2f", val));
if (!Double.isNaN(z))
zText.setText(String.format("%.2f", z));
// draw trace line
if (showTraceLine && linePoints.size() > 1) {
g.setColor(traceHighlight);
int sz = linePoints.size() - 1;
for (int i = 0; i < sz; ++i)
g.drawLine((int)linePoints.get(i).x, (int)linePoints.get(i).y, (int)linePoints.get(i + 1).x, (int)linePoints.get(i + 1).y);
}
// mark cells used in interpolation
if (showInterpolationCells) {
interpRectangle.x = (int)xPos;
interpRectangle.y = (int)yPos;
interpRectangle.width = (int)(cellWidth * xMult);
interpRectangle.height = (int)(diameter * yMult);
g.setColor(cellHighlight);
g.fillRect(interpRectangle.x, interpRectangle.y, interpRectangle.width, interpRectangle.height);
g.setColor(Color.BLACK);
g.drawRect(interpRectangle.x, interpRectangle.y, interpRectangle.width, interpRectangle.height);
}
// mark most significant cell
Graphics2D g2 = (Graphics2D)g;
if (showSiginficantCell) {
g2.setColor(borderHighlight);
g2.draw(signifRectangle);
}
// draw current value point
x = (x0 == x1) ? 0 : (x - x0) / (x1 - x0); // calculate % of x change
y = (y0 == y1) ? 0 : (y - y0) / (y1 - y0); // calculate % of y change
pointCircle.x = (xPos - radius + cellWidth / 2) + cellWidth * x; // calculate x
pointCircle.y = yPos + diameter * y; // calculate y
pointCircle.width = pointCircle.height = diameter;
pt = new LinePoint(pointCircle.x + radius, pointCircle.y + radius);
g2.setColor(highlight);
g2.fill(pointCircle);
g2.setColor(Color.BLACK);
g2.draw(pointCircle);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
};
setGlassPane(glasspanel);
glasspanel.setOpaque(false);
glasspanel.setVisible(true);
}
private void createDataPanel() {
dataPanel = new JPanel();
GridBagLayout gbl_dataPanel = new GridBagLayout();
gbl_dataPanel.columnWidths = new int[] {0, 0};
gbl_dataPanel.rowHeights = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
gbl_dataPanel.columnWeights = new double[]{1.0, 0.0};
gbl_dataPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0};
dataPanel.setLayout(gbl_dataPanel);
getContentPane().add(dataPanel);
addTable(gbl_dataPanel.rowHeights.length);
addLabel(0, "X-Axis");
xText = addTextBox(1);
addLabel(2, "Y-Axis");
yText = addTextBox(3);
addLabel(4, "Calc Value");
valueText = addTextBox(5);
addLabel(6, "Log Value");
zText = addTextBox(7);
loadButton = addButton(8, "Load");
saveButton = addButton(9, "Save");
}
private void addTable(int rowCount) {
playTable = new JTable();
playTable.setColumnSelectionAllowed(true);
playTable.setCellSelectionEnabled(true);
playTable.setBorder(new LineBorder(new Color(0, 0, 0)));
playTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
playTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
playTable.setModel(new DefaultTableModel(RowColumnCount, RowColumnCount));
playTable.setTableHeader(null);
playTable.putClientProperty("terminateEditOnFocusLost", true);
Utils.initializeTable(playTable, ColumnWidth);
excelAdapter.addTable(playTable, true, true, false, false, true, false, true, true, true);
GridBagConstraints gbc_playTable = new GridBagConstraints();
gbc_playTable.anchor = GridBagConstraints.PAGE_START;
gbc_playTable.insets = new Insets(0, 0, 0, 0);
gbc_playTable.fill = GridBagConstraints.BOTH;
gbc_playTable.weightx = 1.0;
gbc_playTable.weighty = 1.0;
gbc_playTable.gridx = 0;
gbc_playTable.gridy = 0;
gbc_playTable.gridheight = rowCount;
dataPanel.add(playTable, gbc_playTable);
}
private void addLabel(int row, String text) {
JLabel label = new JLabel(text);
GridBagConstraints gbc_label = new GridBagConstraints();
gbc_label.anchor = GridBagConstraints.SOUTHWEST;
gbc_label.insets = insetsLabel;
gbc_label.gridx = 1;
gbc_label.gridy = row;
dataPanel.add(label, gbc_label);
}
private JTextField addTextBox(int row) {
JTextField text = new JTextField();
text.setBackground(Color.WHITE);
text.setColumns(6);
text.setEditable(false);
text.setText("0");
text.setHorizontalAlignment(SwingConstants.RIGHT);
GridBagConstraints gbc_text = new GridBagConstraints();
gbc_text.anchor = GridBagConstraints.NORTHWEST;
gbc_text.insets = insetsText;
gbc_text.gridx = 1;
gbc_text.gridy = row;
dataPanel.add(text, gbc_text);
return text;
}
private JButton addButton(int row, String action) {
JButton button = new JButton(action);
GridBagConstraints gbc_button = new GridBagConstraints();
gbc_button.anchor = GridBagConstraints.NORTHWEST;
gbc_button.insets = insetsLabel;
gbc_button.gridx = 1;
gbc_button.gridy = row;
button.addActionListener(this);
dataPanel.add(button, gbc_button);
return button;
}
private void clearTable() {
while (RowColumnCount < playTable.getRowCount())
Utils.removeRow(RowColumnCount, playTable);
while (RowColumnCount < playTable.getColumnCount())
Utils.removeColumn(RowColumnCount, playTable);
Utils.clearTable(playTable);
}
private void load() {
if (JFileChooser.APPROVE_OPTION != fileChooser.showOpenDialog(this))
return;
File file = fileChooser.getSelectedFile();
BufferedReader br = null;
try {
clearTable();
br = new BufferedReader(new InputStreamReader(new FileInputStream(file.getAbsoluteFile()), Config.getEncoding()));
String line = br.readLine();
if (line == null || !line.equals(SaveDataFileHeader)) {
JOptionPane.showMessageDialog(null, "Invalid Log Play Table file!", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
line = br.readLine();
int row = 0;
String[] elements;
while (line != null) {
elements = line.split(Utils.fileFieldSplitter, -1);
Utils.ensureRowCount(row + 1, playTable);
Utils.ensureColumnCount(elements.length - 1, playTable);
for (int i = 0; i < elements.length - 1; ++i)
playTable.setValueAt(elements[i], row, i);
++row;
line = br.readLine();
}
validateTable(playTable);
pack();
}
catch (FileNotFoundException e) {
logger.error(e);
}
catch (Exception e) {
e.printStackTrace();
logger.error(e);
}
finally {
if (br != null) {
try {
br.close();
}
catch (IOException e) {
logger.error(e);
}
}
}
}
private void save() {
if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog(this))
return;
File file = fileChooser.getSelectedFile();
if (!file.getPath().endsWith(".play"))
file = new File(file.getPath() + ".play");
Writer out = null;
try {
int i, j;
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Config.getEncoding()));
out.write(SaveDataFileHeader + "\n");
for (i = 0; i < playTable.getRowCount(); ++i) {
for (j = 0; j < playTable.getColumnCount(); ++j)
out.write(playTable.getValueAt(i, j).toString() + ",");
out.write("\n");
}
}
catch (Exception e) {
e.printStackTrace();
logger.error(e);
JOptionPane.showMessageDialog(null, "Failed to save table:\n\n" + e, "Error", JOptionPane.ERROR_MESSAGE);
}
finally {
if (out != null) {
try {
out.close();
}
catch (IOException e) {
logger.error(e);
}
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == loadButton)
load();
else if (e.getSource() == saveButton)
save();
}
}