/*
* Copyright (c) 2005 (Mike) Maurice Kienenberger (mkienenb@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.gamenet.swing.controls;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.TransferHandler;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import org.gamenet.util.ByteConversions;
public class TextMapTableControl extends JPanel
{
private final ArrayDataTableModel dataTableModel;
private long displayedOffset = 0;
public TextMapTableControl(float[][] floatData, int displayedOffset)
{
super(new BorderLayout());
this.displayedOffset = displayedOffset;
this.dataTableModel = new FloatArrayDataTableModel(floatData);
initialize(floatData[0].length);
}
public TextMapTableControl(int[][] intData, int displayedOffset)
{
super(new BorderLayout());
this.displayedOffset = displayedOffset;
this.dataTableModel = new IntArrayDataTableModel(intData);
initialize(intData[0].length);
}
public TextMapTableControl(short[][] shortData, int displayedOffset)
{
super(new BorderLayout());
this.displayedOffset = displayedOffset;
this.dataTableModel = new ShortArrayDataTableModel(shortData);
initialize(shortData[0].length);
}
public TextMapTableControl(byte[][] byteData, long displayedOffset)
{
super(new BorderLayout());
int byteDataColumns = byteData[0].length;
this.displayedOffset = displayedOffset;
this.dataTableModel = new ByteArrayDataTableModel(byteData);
initialize(byteData[0].length);
}
public void initialize(int dataColumns)
{
final ResizingJTable dataTable = new ResizingJTable(dataTableModel)
{
public String getToolTipText(MouseEvent e) {
String tip = null;
java.awt.Point p = e.getPoint();
int rowIndex = rowAtPoint(p);
int colIndex = columnAtPoint(p);
int realColumnIndex = convertColumnIndexToModel(colIndex);
if ((0 == realColumnIndex) || (0 == rowIndex)) return null;
return "(" + getValueAt(0, realColumnIndex) + "," + getValueAt(rowIndex, 0) + ")";
}
// Doesn't seem to work
// public TableCellRenderer getCellRenderer(int row, int column) {
// if ((row == 0) || (column == 0)) {
// TableCellRenderer renderer = super.getCellRenderer(row, column);
// DefaultTableCellRenderer dr = (DefaultTableCellRenderer)renderer;
// dr.setFont(dr.getFont().deriveFont(Font.BOLD));
// return dr;
// }
// // else...
// return super.getCellRenderer(row, column);
// }
};
JComboBox displayModeComboBox = new JComboBox(dataTableModel.displayModeNameArray);
displayModeComboBox.setSelectedIndex(this.dataTableModel.getDisplayMode());
displayModeComboBox.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent anActionEvent)
{
JComboBox cb = (JComboBox)anActionEvent.getSource();
dataTableModel.setDisplayMode(cb.getSelectedIndex());
dataTable.recalcColumnWidths();
}
}
);
final JButton copyToClipboardTextFieldButton = new JButton("Copy to clipboard");
TransferHandler handler = new TransferHandler("text")
{
protected Transferable createTransferable(JComponent c) {
return new StringSelection(dataTableModel.exportDataAsCSV());
}
};
copyToClipboardTextFieldButton.setTransferHandler(handler);
copyToClipboardTextFieldButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent anActionEvent)
{
Action copyAction = TransferHandler.getCopyAction();
copyAction.actionPerformed(new ActionEvent(copyToClipboardTextFieldButton,
ActionEvent.ACTION_PERFORMED,
null));
}
}
);
JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
controlPanel.add(displayModeComboBox);
controlPanel.add(copyToClipboardTextFieldButton);
controlPanel.add(new JLabel("Offset: " + displayedOffset));
JPanel dataTablePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
dataTablePanel.add(dataTable);
this.add(controlPanel, BorderLayout.PAGE_START);
this.add(dataTablePanel, BorderLayout.CENTER);
dataTable.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
JTable table = (JTable) e.getSource();
TableColumnModel columnModel = table.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = columnModel.getColumn(viewColumn).getModelIndex();
if (0 == column)
{
dataTableModel.changeOffsetDisplay();
}
}
});
}
/**
* @param message
* @param title
*/
private void displayError(String message, String title)
{
JOptionPane.showMessageDialog(this.getRootPane(), message, title, JOptionPane.ERROR_MESSAGE);
}
abstract class ArrayDataTableModel extends AbstractTableModel
{
static public final int BINARY_MODE = 0;
static public final int SIGNED_BYTE_MODE = 1;
static public final int CHARACTER_MODE = 2;
static public final int OCTAL_MODE = 3;
static public final int DECIMAL_MODE = 4;
static public final int HEX_MODE = 5;
/**
* Object array of String, Integer pairs
*/
public final Object displayModeArray[] = new Object[] {
new Object[] { "Binary", new Integer(BINARY_MODE) } ,
new Object[] { "Signed Byte", new Integer(SIGNED_BYTE_MODE) } ,
new Object[] { "Character", new Integer(CHARACTER_MODE) } ,
new Object[] { "Octal", new Integer(OCTAL_MODE) } ,
new Object[] { "Decimal", new Integer(DECIMAL_MODE) } ,
new Object[] { "Hex", new Integer(HEX_MODE) }
};
public final String displayModeNameArray[] = new String[] {
"Binary",
"Signed Byte",
"Character",
"Octal",
"Decimal",
"Hex"
};
private int offsetDisplayMode = HEX_MODE;
protected int displayMode = HEX_MODE;
final int dataRowCount;
final int dataColumnCount;
public ArrayDataTableModel(int dataRowCount, int dataColumnCount)
{
super();
this.dataRowCount = dataRowCount;
this.dataColumnCount = dataColumnCount;
}
public int getDisplayMode()
{
return this.displayMode;
}
public void setDisplayMode(int displayMode)
{
this.displayMode = displayMode;
this.fireTableDataChanged();
}
public abstract Object getDataValueAt(int row, int column);
public abstract void setDataValueAt(Object value, int row, int column);
public String getColumnName(int col)
{
if (0 == col) return null;
return String.valueOf(512 * ((col - 1) - (this.dataColumnCount/2))) + ":";
}
public int getRowCount()
{
return 1 + this.dataRowCount;
}
public int getColumnCount()
{
return 1 + this.dataColumnCount;
}
public String exportDataAsCSV()
{
String dataString = "";
for (int row = 1; row < getRowCount(); ++row)
{
for (int col = 1; col < getColumnCount(); ++col)
{
Object value = getValueAt(row, col);
if (value != null) dataString += String.valueOf(value);
dataString += "\t";
}
dataString += "\n";
}
return dataString;
}
public Object getValueAt(int realRow, int realColumn)
{
if (0 == realRow) return getColumnName(realColumn);
if (0 == realColumn)
{
return String.valueOf(-512 * ((realRow - 1) - (this.dataRowCount/2))) + ":";
}
return getDataValueAt(realRow - 1, realColumn -1);
}
public boolean isCellEditable(int row, int realColumn)
{
if (0 == realColumn) return false;
return true;
}
public void setValueAt(Object value, int realRow, int realColumn)
{
if (0 == realRow)
{
displayError("Illegal row,column pair [" + realRow + "," + realColumn + "]", "Error");
return;
}
if (0 == realColumn)
{
displayError("Illegal row,column pair [" + realRow + "," + realColumn + "]", "Error");
return;
}
Object oldValue = getDataValueAt(realRow - 1, realColumn - 1);
setDataValueAt(value, realRow - 1, realColumn - 1);
Object newValue = getDataValueAt(realRow - 1, realColumn - 1);
if ((null == oldValue) && (null != newValue))
{
fireTableCellUpdated(realRow - 1, realColumn - 1);
}
else if (false == oldValue.equals(newValue))
{
fireTableCellUpdated(realRow - 1, realColumn - 1);
}
}
protected void changeOffsetDisplay()
{
if (DECIMAL_MODE == offsetDisplayMode)
offsetDisplayMode = HEX_MODE;
else offsetDisplayMode = DECIMAL_MODE;
fireTableDataChanged();
}
protected int convertValueFromString(int displayMode, String value)
{
switch(displayMode)
{
case BINARY_MODE:
int binaryValue = -1;
try
{
binaryValue = Integer.parseInt(value, 2);
}
catch (NumberFormatException exception)
{
displayError("Illegal value: '" + value + "' is not a binary number", "Error");
return -1;
}
if ((binaryValue > 255) || (binaryValue < 0))
{
displayError("Illegal value: binary number must be between 0 and 11111111", "Error");
return -1;
}
return binaryValue;
case SIGNED_BYTE_MODE:
int signedIntValue = -129;
try
{
signedIntValue = Integer.parseInt(value);
}
catch (NumberFormatException exception)
{
displayError("Illegal value: '" + value + "' is not a signed byte number", "Error");
return -1;
}
if ((signedIntValue > 127) || (signedIntValue < -128))
{
displayError("Illegal value: signed byte number must be between 0 and 255", "Error");
return -1;
}
return ByteConversions.convertByteToInt((byte)signedIntValue);
case DECIMAL_MODE:
int intValue = -1;
try
{
intValue = Integer.parseInt(value);
}
catch (NumberFormatException exception)
{
displayError("Illegal value: '" + value + "' is not a decimal number", "Error");
return -1;
}
if ((intValue > 255) || (intValue < 0))
{
displayError("Illegal value: decimal number must be between 0 and 255", "Error");
return -1;
}
return intValue;
case CHARACTER_MODE:
if (value.length() != 1)
{
displayError("Illegal value: only one character allowed", "Error");
return -1;
}
return (byte)(value.charAt(0));
case OCTAL_MODE:
int octalValue = -1;
try
{
octalValue = Integer.parseInt(value, 8);
}
catch (NumberFormatException exception)
{
displayError("Illegal value: '" + value + "' is not a octal number", "Error");
return -1;
}
if ((octalValue > 255) || (octalValue < 0))
{
displayError("Illegal value: octal number must be between 0 and 377", "Error");
return -1;
}
return octalValue;
case HEX_MODE:
int hexValue = -1;
try
{
hexValue = Integer.parseInt(value, 16);
}
catch (NumberFormatException exception)
{
displayError("Illegal value: '" + value + "' is not a hex number", "Error");
return -1;
}
if ((hexValue > 255) || (hexValue < 0))
{
displayError("Illegal value: hex number must be between 0 and ff", "Error");
return -1;
}
return hexValue;
default:
displayError("Unsupported Display Mode: " + displayMode, "Error");
return -1;
}
}
}
class ByteArrayDataTableModel extends ArrayDataTableModel
{
byte byteData[][] = null;
public ByteArrayDataTableModel(byte byteDataArray[][])
{
super(byteDataArray.length, byteDataArray[0].length);
this.byteData = byteDataArray;
}
public Object getDataValueAt(int row, int column)
{
byte aByte = byteData[row][column];
if (BINARY_MODE == displayMode)
{
StringBuffer binaryOutput = new StringBuffer(8);
for (int i = 7; i >= 0; --i)
binaryOutput.append((aByte & 1 << i) != 0 ? '1' : '0');
return binaryOutput.toString();
}
else if (SIGNED_BYTE_MODE == displayMode)
{
return new Byte(aByte);
}
else if (DECIMAL_MODE == displayMode)
{
return new Integer(aByte >= 0 ? aByte : (256 + ((int)aByte)));
}
else if (CHARACTER_MODE == displayMode)
{
Character ch = new Character((char)(aByte >= 0 ? aByte : (256 + ((int)aByte))));
if (false == Character.isDefined(ch.charValue())) return "..";
if (Character.isISOControl(ch.charValue())) return "..";
return ch;
}
else if (OCTAL_MODE == displayMode)
{
return Integer.toOctalString(aByte >= 0 ? aByte : (256 + ((int)aByte)));
}
else if (HEX_MODE == displayMode)
{
return Integer.toHexString(aByte >= 0 ? aByte : (256 + ((int)aByte)));
}
else
{
displayError("Unsupported Display Mode: " + displayMode, "Error");
return "";
}
}
public void setDataValueAt(Object value, int row, int column)
{
byte newByteValue = 0;
if (value instanceof Number)
{
newByteValue = ((Number)value).byteValue();
}
else if (value instanceof Character)
{
newByteValue = (byte)(((Character)value).charValue());
}
else if (value instanceof String)
{
String stringValue = (String)value;
int intValue = convertValueFromString(displayMode, stringValue);
// Error already reported
if (-1 == intValue) return;
newByteValue = ByteConversions.convertIntToByte(intValue);
}
else
{
displayError("Illegal value class: " + value.getClass().getName(), "Error");
return;
}
byteData[row][column] = newByteValue;
}
}
class ShortArrayDataTableModel extends ArrayDataTableModel
{
short shortData[][] = null;
public ShortArrayDataTableModel(short shortDataArray[][])
{
super(shortDataArray.length, shortDataArray[0].length);
this.shortData = shortDataArray;
}
public Object getDataValueAt(int row, int column)
{
short aByte = shortData[row][column];
if (BINARY_MODE == displayMode)
{
StringBuffer binaryOutput = new StringBuffer(16);
for (int i = 15; i >= 0; --i)
binaryOutput.append((aByte & 1 << i) != 0 ? '1' : '0');
return binaryOutput.toString();
}
else if (SIGNED_BYTE_MODE == displayMode)
{
return new Short(aByte);
}
else if (DECIMAL_MODE == displayMode)
{
return new Integer(aByte);
}
else if (OCTAL_MODE == displayMode)
{
return Integer.toOctalString(aByte);
}
else if (HEX_MODE == displayMode)
{
return Integer.toHexString(aByte);
}
else
{
displayError("Unsupported Display Mode: " + displayMode, "Error");
return "";
}
}
public boolean isCellEditable(int row, int realColumn)
{
return false;
}
public void setDataValueAt(Object value, int row, int column)
{
displayError("Editing disallowed.", "Error");
return;
// short newShortValue = 0;
//
// if (value instanceof Number)
// {
// newShortValue = ((Number)value).shortValue();
// }
// else if (value instanceof String)
// {
// String stringValue = (String)value;
// int intValue = convertValueFromString(displayMode, stringValue);
//
// // Error already reported
// if (-1 == intValue) return;
//
// newShortValue = ByteConversions.convertIntToShort(intValue);
// }
// else
// {
// displayError("Illegal value class: " + value.getClass().getName(), "Error");
// return;
// }
//
// shortData[row][column] = newShortValue;
}
}
class IntArrayDataTableModel extends ArrayDataTableModel
{
int intData[][] = null;
public IntArrayDataTableModel(int intDataArray[][])
{
super(intDataArray.length, intDataArray[0].length);
this.intData = intDataArray;
}
public Object getDataValueAt(int row, int column)
{
int aByte = intData[row][column];
if (BINARY_MODE == displayMode)
{
StringBuffer binaryOutput = new StringBuffer(16);
for (int i = 15; i >= 0; --i)
binaryOutput.append((aByte & 1 << i) != 0 ? '1' : '0');
return binaryOutput.toString();
}
else if (SIGNED_BYTE_MODE == displayMode)
{
return new Integer(aByte);
}
else if (DECIMAL_MODE == displayMode)
{
return new Integer(aByte);
}
else if (OCTAL_MODE == displayMode)
{
return Integer.toOctalString(aByte);
}
else if (HEX_MODE == displayMode)
{
return Integer.toHexString(aByte);
}
else
{
displayError("Unsupported Display Mode: " + displayMode, "Error");
return "";
}
}
public boolean isCellEditable(int row, int realColumn)
{
return false;
}
public void setDataValueAt(Object value, int row, int column)
{
displayError("Editing disallowed.", "Error");
return;
// int newShortValue = 0;
//
// if (value instanceof Number)
// {
// newShortValue = ((Number)value).intValue();
// }
// else if (value instanceof String)
// {
// String stringValue = (String)value;
// int intValue = convertValueFromString(displayMode, stringValue);
//
// // Error already reported
// if (-1 == intValue) return;
//
// newShortValue = ByteConversions.convertIntToShort(intValue);
// }
// else
// {
// displayError("Illegal value class: " + value.getClass().getName(), "Error");
// return;
// }
//
// intData[row][column] = newShortValue;
}
}
class FloatArrayDataTableModel extends ArrayDataTableModel
{
float floatData[][] = null;
public FloatArrayDataTableModel(float floatDataArray[][])
{
super(floatDataArray.length, floatDataArray[0].length);
this.floatData = floatDataArray;
}
public Object getDataValueAt(int row, int column)
{
float value = floatData[row][column];
return new Float(value);
}
public boolean isCellEditable(int row, int realColumn)
{
return false;
}
public void setDataValueAt(Object value, int row, int column)
{
displayError("Editing disallowed.", "Error");
return;
// int newShortValue = 0;
//
// if (value instanceof Number)
// {
// newShortValue = ((Number)value).intValue();
// }
// else if (value instanceof String)
// {
// String stringValue = (String)value;
// int intValue = convertValueFromString(displayMode, stringValue);
//
// // Error already reported
// if (-1 == intValue) return;
//
// newShortValue = ByteConversions.convertIntToShort(intValue);
// }
// else
// {
// displayError("Illegal value class: " + value.getClass().getName(), "Error");
// return;
// }
//
// intData[row][column] = newShortValue;
}
}
}