/*
* 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 java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.TransferHandler;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import org.gamenet.application.mm8leveleditor.data.DateTime;
import org.gamenet.util.ByteConversions;
public class ComparativeTableControl extends JPanel
{
private final ComparativeByteDataTableModel byteDataTableModel;
private long displayedOffset = 0;
private JPopupMenu popup = null;
private JMenuItem splitMenuItem = null;
private JMenuItem mergeMenuItem = null;
private JMenuItem nameAndLockOffsetDataMenuItem = null;
private JMenuItem unlockOffsetDataMenuItem = null;
public ComparativeTableControl(List offsetList, DataSource dataSource)
{
super(new BorderLayout());
this.byteDataTableModel = new ComparativeByteDataTableModel(offsetList, dataSource);
final ResizingJTable dataTable = new ResizingJTable(byteDataTableModel)
{
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);
return byteDataTableModel.getCellTooltip(rowIndex, realColumnIndex);
}
};
final JButton copyToClipboardTextFieldButton = new JButton("Copy to clipboard");
TransferHandler handler = new TransferHandler("text")
{
protected Transferable createTransferable(JComponent c) {
return new StringSelection(byteDataTableModel.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(copyToClipboardTextFieldButton);
controlPanel.add(new JLabel(" Right-click to merge or split data."));
JPanel dataTablePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
dataTablePanel.add(dataTable);
this.add(controlPanel, BorderLayout.PAGE_START);
this.add(dataTablePanel, BorderLayout.CENTER);
popup = new JPopupMenu();
splitMenuItem = new JMenuItem("Split before column");
splitMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
byteDataTableModel.splitOffsetData();
}
});
popup.add(splitMenuItem);
mergeMenuItem = new JMenuItem("Merge columns");
mergeMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
byteDataTableModel.mergeOffsetData();
}
});
popup.add(mergeMenuItem);
nameAndLockOffsetDataMenuItem = new JMenuItem("Name and lock column");
nameAndLockOffsetDataMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
byteDataTableModel.nameAndLockOffsetData();
}
});
popup.add(nameAndLockOffsetDataMenuItem);
unlockOffsetDataMenuItem = new JMenuItem("Unlock column");
unlockOffsetDataMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
byteDataTableModel.unlockOffsetData();
}
});
popup.add(unlockOffsetDataMenuItem);
dataTable.addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
if (false == maybeShowPopup(e)) {
JTable table = (JTable) e.getSource();
TableColumnModel columnModel = table.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = columnModel.getColumn(viewColumn).getModelIndex();
int row = table.rowAtPoint(e.getPoint());
byteDataTableModel.mouseClickAt(row, column);
}
}
private boolean maybeShowPopup(MouseEvent e)
{
if (e.isPopupTrigger())
{
JTable table = (JTable) e.getSource();
TableColumnModel columnModel = table.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = columnModel.getColumn(viewColumn).getModelIndex();
int row = table.rowAtPoint(e.getPoint());
byteDataTableModel.setRowAndColumnOnPopup(row, column);
byteDataTableModel.hasValidPopupMenuItems(row, column);
popup.show(e.getComponent(),
e.getX(), e.getY());
return true;
}
else return false;
}
});
}
public AbstractTableModel getTableModel()
{
return this.byteDataTableModel;
}
/**
* @param message
* @param title
*/
private void displayError(String message, String title)
{
JOptionPane.showMessageDialog(this.getRootPane(), message, title, JOptionPane.ERROR_MESSAGE);
}
/**
* @param message
* @param title
*/
private String displayInputPanel(String message, String title)
{
return (String)JOptionPane.showInputDialog(
this.getRootPane(),
message,
title,
JOptionPane.PLAIN_MESSAGE,
null,
null,
null);
}
public interface DataSource
{
public int getRowCount();
public byte[] getData(int dataRow);
public int getAdjustedDataRowIndex(int dataRow);
public String getIdentifier(int dataRow);
public int getOffset(int dataRow);
}
public static class PartialComparativeDataSource implements DataSource
{
private Map adjustedDataRowHashMap = new HashMap();
private DataSource dataSource = null;
public PartialComparativeDataSource(DataSource dataSource, List indexList)
{
super();
this.dataSource = dataSource;
int dataRow = 0;
Iterator indexIterator = indexList.iterator();
while (indexIterator.hasNext())
{
Integer indexInteger = (Integer) indexIterator.next();
int index = indexInteger.intValue();
adjustedDataRowHashMap.put(new Integer(dataRow), new Integer(index));
dataRow++;
}
}
public int getRowCount()
{
return adjustedDataRowHashMap.size();
}
public byte[] getData(int dataRow)
{
Integer dataRowNumber = new Integer(dataRow);
Integer adjustedDataRowNumber = (Integer)adjustedDataRowHashMap.get(dataRowNumber);
int adjustedDataRow = adjustedDataRowNumber.intValue();
return dataSource.getData(adjustedDataRow);
}
public int getAdjustedDataRowIndex(int dataRow)
{
Integer dataRowNumber = new Integer(dataRow);
Integer adjustedDataRowNumber = (Integer)adjustedDataRowHashMap.get(dataRowNumber);
return adjustedDataRowNumber.intValue();
}
public String getIdentifier(int dataRow)
{
Integer dataRowNumber = new Integer(dataRow);
Integer adjustedDataRowNumber = (Integer)adjustedDataRowHashMap.get(dataRowNumber);
int adjustedDataRow = adjustedDataRowNumber.intValue();
return dataSource.getIdentifier(adjustedDataRow);
}
public int getOffset(int dataRow)
{
Integer dataRowNumber = new Integer(dataRow);
Integer adjustedDataRowNumber = (Integer)adjustedDataRowHashMap.get(dataRowNumber);
int adjustedDataRow = adjustedDataRowNumber.intValue();
return dataSource.getOffset(adjustedDataRow);
}
};
private class InternalModelDataSource
{
private DataSource dataObjectDataSource = null;
public InternalModelDataSource(DataSource dataObjectDataSource)
{
this.dataObjectDataSource = dataObjectDataSource;
}
public int getRowCount()
{
return this.dataObjectDataSource.getRowCount();
}
public int getAdjustedDataRowIndex(int dataRow)
{
return this.dataObjectDataSource.getAdjustedDataRowIndex(dataRow);
}
public String getRowIdentifier(int dataRow)
{
return this.dataObjectDataSource.getIdentifier(dataRow);
}
public long getRowDataAsLongAtPosition(int dataRow, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 8) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
return ByteConversions.getLongInByteArrayAtPosition(dataObjectDataSource.getData(dataRow), offset);
}
public float getRowDataAsFloatAtPosition(int dataRow, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 4) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
return ByteConversions.getFloatInByteArrayAtPosition(dataObjectDataSource.getData(dataRow), offset);
}
public long getRowDataAsUnsignedIntegerAtPosition(int dataRow, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 4) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
return ByteConversions.getIntegerInByteArrayAtPosition(dataObjectDataSource.getData(dataRow), offset);
}
public int getRowDataAsUnsignedShortAtPosition(int dataRow, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 2) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
return ByteConversions.getUnsignedShortInByteArrayAtPosition(dataObjectDataSource.getData(dataRow), offset);
}
public int getRowDataAsSignedShortAtPosition(int dataRow, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 2) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
return ByteConversions.getUnsignedShortInByteArrayAtPosition(dataObjectDataSource.getData(dataRow), offset);
}
public int getRowDataAsUnsignedByteAtPosition(int dataRow, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if (offset > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
return ByteConversions.convertByteToInt(dataObjectDataSource.getData(dataRow)[offset]);
}
public int getRowDataAsSignedByteAtPosition(int dataRow, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if (offset > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
return dataObjectDataSource.getData(dataRow)[offset];
}
public String getRowDataAsZeroTerminatedStringAtPosition(int dataRow, int index, int maxLength)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + maxLength) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds with maxLength <" + maxLength + ">.");
return ByteConversions.getZeroTerminatedStringInByteArrayAtPositionMaxLength(dataObjectDataSource.getData(dataRow), offset, maxLength);
}
public void setRowDataAsLongAtPosition(int dataRow, long value, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 8) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
ByteConversions.setLongInByteArrayAtPosition(value, dataObjectDataSource.getData(dataRow), offset);
}
public void setRowDataAsFloatAtPosition(int dataRow, long value, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 4) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
ByteConversions.setFloatInByteArrayAtPosition(value, dataObjectDataSource.getData(dataRow), offset);
}
public void setRowDataAsUnsignedIntegerAtPosition(int dataRow, long value, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 4) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
ByteConversions.setIntegerInByteArrayAtPosition(value, dataObjectDataSource.getData(dataRow), offset);
}
public void setRowDataAsSignedShortAtPosition(int dataRow, int value, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 2) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
ByteConversions.setShortInByteArrayAtPosition((short)value, dataObjectDataSource.getData(dataRow), offset);
}
public void setRowDataAsUnsignedShortAtPosition(int dataRow, int value, int index)
{
// IMPLEMENT: setDataAsUnsignedShortAtPosition
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + 2) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
ByteConversions.setShortInByteArrayAtPosition((short)value, dataObjectDataSource.getData(dataRow), offset);
}
public void setRowDataAsUnsignedByteAtPosition(int dataRow, int value, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if (offset > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
dataObjectDataSource.getData(dataRow)[offset] = ByteConversions.convertIntToByte(value);
}
public void setRowDataAsSignedByteAtPosition(int dataRow, int value, int index)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if (offset > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds.");
dataObjectDataSource.getData(dataRow)[offset] = (byte)value;
}
public void setRowDataAsZeroTerminatedStringAtPosition(int dataRow, String value, int index, int maxLength)
{
int offset = index - dataObjectDataSource.getOffset(dataRow);
if ((offset + maxLength) > dataObjectDataSource.getData(dataRow).length)
throw new RuntimeException("index <" + index + "> out of bounds with maxLength <" + maxLength + ">.");
ByteConversions.setZeroTerminatedStringInByteArrayAtPositionMaxLength(value, dataObjectDataSource.getData(dataRow), offset, maxLength);
}
}
public static class OffsetData
{
private int offset;
private int representation;
private int byteLength;
private String offsetDataName;
public OffsetData(int offset, int byteLength)
{
this(offset, byteLength, REPRESENTATION_INT_HEX, null);
}
public OffsetData(int offset, int byteLength, int representation)
{
this(offset, byteLength, representation, null);
}
public OffsetData(int offset, int byteLength, int representation, String offsetDataName)
{
super();
this.offset = offset;
this.representation = bestRepresentationForByteLength(representation, byteLength);
this.byteLength = byteLength;
this.offsetDataName = offsetDataName;
switch (this.representation)
{
// 8-byte reps
case REPRESENTATION_TIME:
if (0 != (byteLength % 8)) throw new RuntimeException("Invalid representation <" + this.representation + "> for byteLength <" + byteLength + ">");
break;
// 4-byte reps
case REPRESENTATION_INT_HEX:
case REPRESENTATION_INT_DEC:
case REPRESENTATION_FLOAT_DEC:
if (0 != (byteLength % 4)) throw new RuntimeException("Invalid representation <" + this.representation + "> for byteLength <" + byteLength + ">");
break;
// 2-byte reps
case REPRESENTATION_SHORT_HEX:
case REPRESENTATION_SHORT_DEC:
if (0 != (byteLength % 2)) throw new RuntimeException("Invalid representation <" + this.representation + "> for byteLength <" + byteLength + ">");
break;
// 1-byte reps
case REPRESENTATION_BYTE_HEX:
case REPRESENTATION_BYTE_DEC:
case REPRESENTATION_STRING:
if (byteLength < 1) throw new RuntimeException("Invalid representation <" + this.representation + "> for byteLength <" + byteLength + ">");
break;
default:
throw new RuntimeException("Illegal representation <" + this.representation + ">");
}
}
public int getByteLength()
{
return this.byteLength;
}
public void setByteLength(int byteLength)
{
this.byteLength = byteLength;
}
public int getRepresentation()
{
return this.representation;
}
public void setRepresentation(int representation)
{
this.representation = representation;
}
public int getOffset()
{
return this.offset;
}
public void setOffset(int offset)
{
this.offset = offset;
}
public String getOffsetDataName()
{
return this.offsetDataName;
}
private static int bestRepresentationForByteLength(int oldRepresentation, int byteLength)
{
int bestRepresentation = oldRepresentation;
switch (bestRepresentation)
{
// 8-byte reps
case REPRESENTATION_TIME:
if (0 == (byteLength % 8)) return bestRepresentation;
bestRepresentation = REPRESENTATION_INT_HEX;
break;
}
switch (bestRepresentation)
{
// 4-byte reps
case REPRESENTATION_INT_HEX:
if (0 == (byteLength % 4)) return bestRepresentation;
bestRepresentation = REPRESENTATION_SHORT_HEX;
break;
case REPRESENTATION_INT_DEC:
if (0 == (byteLength % 4)) return bestRepresentation;
bestRepresentation = REPRESENTATION_SHORT_DEC;
break;
case REPRESENTATION_FLOAT_DEC:
if (0 == (byteLength % 4)) return bestRepresentation;
bestRepresentation = REPRESENTATION_FLOAT_DEC;
break;
}
switch (bestRepresentation)
{
// 2-byte reps
case REPRESENTATION_SHORT_HEX:
if (0 == (byteLength % 2)) return bestRepresentation;
bestRepresentation = REPRESENTATION_BYTE_HEX;
break;
case REPRESENTATION_SHORT_DEC:
if (0 == (byteLength % 2)) return bestRepresentation;
bestRepresentation = REPRESENTATION_BYTE_DEC;
break;
}
return bestRepresentation;
}
public boolean canSplit(int internalOffset)
{
if (null != this.getOffsetDataName()) return false;
if ((internalOffset < 1) || (internalOffset >= this.byteLength)) return false;
return true;
}
/**
* Truncates existing OffsetData at internal offset and returns rest of data in new OffsetData object
* @param internalOffset
* @return new OffsetData containing data after internalOffset
*/
public OffsetData split(int internalOffset)
{
if (false == canSplit(internalOffset)) return null;
int newOffset = this.offset + internalOffset;
int newByteLength = this.byteLength - internalOffset;
OffsetData newOffsetData = new OffsetData(newOffset, newByteLength, bestRepresentationForByteLength(this.representation, newByteLength));
int modifiedByteLength = this.byteLength - newByteLength;
int modifiedRepresentation = bestRepresentationForByteLength(this.representation, modifiedByteLength);
this.byteLength = modifiedByteLength;
this.representation = modifiedRepresentation;
return newOffsetData;
}
public boolean canMerge(OffsetData nextOffsetData)
{
if (null == nextOffsetData) return false;
if (null != this.getOffsetDataName()) return false;
if (null != nextOffsetData.getOffsetDataName()) return false;
if ((this.offset + byteLength) != nextOffsetData.getOffset()) return false;
return true;
}
/**
* Merges nextOffsetData with existing OffsetData if data is consecutive.
* nextOffsetData is left unchanged.
* @param nextOffsetData to merge
* @return whether merge was successful
*/
public boolean merge(OffsetData nextOffsetData)
{
if (false == canMerge(nextOffsetData)) return false;
int modifiedByteLength = this.byteLength + nextOffsetData.getByteLength();
int modifiedRepresentation = bestRepresentationForByteLength(this.representation, modifiedByteLength);
this.byteLength = modifiedByteLength;
this.representation = modifiedRepresentation;
return true;
}
public boolean canNameAndLock()
{
return true;
}
public void nameAndLock(String newOffsetDataName)
{
if (false == canNameAndLock()) return;
this.offsetDataName = newOffsetDataName;
}
public boolean canUnlock()
{
return (null != this.offsetDataName);
}
public void unlock()
{
if (false == canUnlock()) return;
this.offsetDataName = null;
}
}
public static final int REPRESENTATION_INT_HEX = 0;
public static final int REPRESENTATION_INT_DEC = 1;
public static final int REPRESENTATION_SHORT_HEX = 2;
public static final int REPRESENTATION_SHORT_DEC = 3;
public static final int REPRESENTATION_BYTE_HEX = 4;
public static final int REPRESENTATION_BYTE_DEC = 5;
public static final int REPRESENTATION_STRING = 6;
public static final int REPRESENTATION_FLOAT_DEC = 7;
public static final int REPRESENTATION_TIME = 8;
class ComparativeByteDataTableModel extends AbstractTableModel implements ResizingJTable.ResizingJTableAdvice
{
static private final int OFFSET_DISPLAY_MODE_ABS_HEX = 0;
static private final int OFFSET_DISPLAY_MODE_ABS_DECIMAL = 1;
static private final int OFFSET_DISPLAY_MODE_REL_HEX = 2;
static private final int OFFSET_DISPLAY_MODE_REL_DECIMAL = 3;
private int offsetDisplayMode = OFFSET_DISPLAY_MODE_ABS_HEX;
private boolean shouldRecalcColumnWidths = true;
private List offsetList = null;
private InternalModelDataSource internalModelDataSource = null;
private int lastRowOnPopup = -1;
private int lastColumnOnPopup = -1;
public ComparativeByteDataTableModel(List offsetList, DataSource dataSource)
{
super();
this.offsetList = offsetList;
this.internalModelDataSource = new InternalModelDataSource(dataSource);
}
private String getOffsetDisplayString(int offsetValue)
{
String offsetString = null;
if ((OFFSET_DISPLAY_MODE_ABS_DECIMAL == offsetDisplayMode) || (OFFSET_DISPLAY_MODE_REL_DECIMAL == offsetDisplayMode))
{
offsetString = String.valueOf(offsetValue);
}
else if ((OFFSET_DISPLAY_MODE_ABS_HEX == offsetDisplayMode) || (OFFSET_DISPLAY_MODE_REL_HEX == offsetDisplayMode))
{
offsetString = "0x" + Long.toHexString(offsetValue);
}
if ((OFFSET_DISPLAY_MODE_REL_HEX == offsetDisplayMode) || (OFFSET_DISPLAY_MODE_REL_DECIMAL == offsetDisplayMode))
{
offsetString += "(r)";
}
return offsetString;
}
private void changeOffsetDisplay()
{
if (OFFSET_DISPLAY_MODE_ABS_HEX == offsetDisplayMode)
offsetDisplayMode = OFFSET_DISPLAY_MODE_ABS_DECIMAL;
else if (OFFSET_DISPLAY_MODE_ABS_DECIMAL == offsetDisplayMode)
offsetDisplayMode = OFFSET_DISPLAY_MODE_REL_HEX;
else if (OFFSET_DISPLAY_MODE_REL_HEX == offsetDisplayMode)
offsetDisplayMode = OFFSET_DISPLAY_MODE_REL_DECIMAL;
else if (OFFSET_DISPLAY_MODE_REL_DECIMAL == offsetDisplayMode)
offsetDisplayMode = OFFSET_DISPLAY_MODE_ABS_HEX;
else
{
displayError("Illegal offsetDisplayMode: " + offsetDisplayMode, "Error");
return;
}
fireTableDataChanged();
}
private int representationColumnCount(int representation, int byteLength)
{
switch (representation)
{
case REPRESENTATION_TIME:
if (0 == (byteLength % 8)) return byteLength / 8;
displayError("Illegal length <" + byteLength + "> for representation <" + representation + ">", "Error");
return -1;
case REPRESENTATION_INT_HEX:
case REPRESENTATION_INT_DEC:
case REPRESENTATION_FLOAT_DEC:
if (0 == (byteLength % 4)) return byteLength / 4;
displayError("Illegal length <" + byteLength + "> for representation <" + representation + ">", "Error");
return -1;
case REPRESENTATION_SHORT_HEX:
case REPRESENTATION_SHORT_DEC:
if (0 == (byteLength % 2)) return byteLength / 2;
displayError("Illegal length <" + byteLength + "> for representation <" + representation + ">", "Error");
return -1;
case REPRESENTATION_BYTE_HEX:
case REPRESENTATION_BYTE_DEC:
return byteLength;
case REPRESENTATION_STRING:
return 1;
default:
displayError("Illegal representation <" + representation + ">", "Error");
return -1;
}
}
private int getRepresentationByteCountPerColumn(int representation, int byteLength)
{
switch (representation)
{
case REPRESENTATION_TIME:
return 8;
case REPRESENTATION_INT_HEX:
case REPRESENTATION_INT_DEC:
case REPRESENTATION_FLOAT_DEC:
return 4;
case REPRESENTATION_SHORT_HEX:
case REPRESENTATION_SHORT_DEC:
return 2;
case REPRESENTATION_BYTE_HEX:
case REPRESENTATION_BYTE_DEC:
return 1;
case REPRESENTATION_STRING:
return byteLength;
default:
displayError("Illegal representation <" + representation + ">", "Error");
return -1;
}
}
public int getColumnCount()
{
int columnCount = 1; // row header/identity
for (int offsetIndex = 0; offsetIndex < offsetList.size(); offsetIndex++)
{
OffsetData offsetData = (OffsetData)offsetList.get(offsetIndex);
columnCount += representationColumnCount(offsetData.getRepresentation(), offsetData.getByteLength());
columnCount += 1; // EMPTY_COLUMN
}
return columnCount;
}
public int getRowCount()
{
return 2 + this.internalModelDataSource.getRowCount();
}
// TODO: optimize by precomputing these values
private OffsetData getOffsetDataForColumn(int dataColumn)
{
int currentColumn = 0;
for (int offsetIndex = 0; offsetIndex < offsetList.size(); offsetIndex++)
{
int startColumn = currentColumn;
OffsetData offsetData = (OffsetData)offsetList.get(offsetIndex);
int columnsOfData = representationColumnCount(offsetData.getRepresentation(), offsetData.getByteLength());
int endColumn = startColumn + columnsOfData; // includes EMPTY_COLUMN at end
if ((dataColumn >= startColumn) && (dataColumn <= endColumn))
return offsetData;
currentColumn = endColumn + 1;
}
displayError("Illegal data column index <" + currentColumn + "> ", "Error");
return null;
}
private int getOffsetInOffsetDataForColumn(int dataColumn)
{
int currentColumn = 0;
for (int offsetIndex = 0; offsetIndex < offsetList.size(); offsetIndex++)
{
int startColumn = currentColumn;
OffsetData offsetData = (OffsetData)offsetList.get(offsetIndex);
int columnsOfData = representationColumnCount(offsetData.getRepresentation(), offsetData.getByteLength());
int endColumn = startColumn + columnsOfData; // includes EMPTY_COLUMN at end
if ((dataColumn >= startColumn) && (dataColumn <= endColumn))
return (dataColumn - startColumn) * getRepresentationByteCountPerColumn(offsetData.getRepresentation(), offsetData.getByteLength());
currentColumn = endColumn + 1;
}
displayError("Illegal data column index <" + currentColumn + "> ", "Error");
return -1;
}
public String getColumnHeader1(int realColumnIndex)
{
if (0 == realColumnIndex) return null;
int dataColumnIndex = realColumnIndex - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
if (offsetData.getByteLength() == offsetInOffsetData) return null; // EMPTY_COLUMN
int offset = offsetData.getOffset() + offsetInOffsetData;
return getOffsetDisplayString(offset);
}
public String getColumnHeader2(int realColumnIndex)
{
if (0 == realColumnIndex) return null;
int dataColumnIndex = realColumnIndex - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
int representationByteCountPerColumn = getRepresentationByteCountPerColumn(offsetData.getRepresentation(), offsetData.getByteLength());
if (offsetData.getByteLength() == offsetInOffsetData) return null; // EMPTY_COLUMN
if (null != offsetData.getOffsetDataName()) return offsetData.getOffsetDataName();
switch (offsetData.getRepresentation())
{
case REPRESENTATION_TIME:
return "Date" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn);
case REPRESENTATION_FLOAT_DEC:
return "Flt" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn) + "(h)";
case REPRESENTATION_INT_HEX:
return "Lg" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn) + "(h)";
case REPRESENTATION_INT_DEC:
return "Lg" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn) + "(d)";
case REPRESENTATION_SHORT_HEX:
return "Sh" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn) + "(h)";
case REPRESENTATION_SHORT_DEC:
return "Sh" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn) + "(d)";
case REPRESENTATION_BYTE_HEX:
return "By" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn) + "(h)";
case REPRESENTATION_BYTE_DEC:
return "By" + String.valueOf(offsetInOffsetData / representationByteCountPerColumn) + "(d)";
case REPRESENTATION_STRING:
return "str[" + String.valueOf(offsetData.getByteLength()) + "]";
default:
displayError("Illegal representation <" + offsetData.getRepresentation() + ">", "Error");
return null;
}
}
public String getDataRowName(int dataRowIndex)
{
return String.valueOf(internalModelDataSource.getAdjustedDataRowIndex(dataRowIndex)) + ": " + internalModelDataSource.getRowIdentifier(dataRowIndex);
}
public void mouseClickAt(int realRowIndex, int realColumnIndex)
{
if ((0 != realRowIndex) && (1 != realRowIndex)) return;
if (0 == realColumnIndex) return;
if (0 == realRowIndex)
{
changeOffsetDisplay();
return;
}
int dataColumnIndex = realColumnIndex - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
int newRepresentation;
switch (offsetData.getRepresentation())
{
// 8-byte reps
case REPRESENTATION_TIME:
newRepresentation = REPRESENTATION_FLOAT_DEC;
break;
// 4-byte reps
case REPRESENTATION_FLOAT_DEC:
newRepresentation = REPRESENTATION_INT_HEX;
break;
case REPRESENTATION_INT_HEX:
newRepresentation = REPRESENTATION_INT_DEC;
break;
case REPRESENTATION_INT_DEC:
newRepresentation = REPRESENTATION_SHORT_HEX;
break;
// 2-byte reps
case REPRESENTATION_SHORT_HEX:
newRepresentation = REPRESENTATION_SHORT_DEC;
break;
case REPRESENTATION_SHORT_DEC:
newRepresentation = REPRESENTATION_BYTE_HEX;
break;
// 1-byte reps
case REPRESENTATION_BYTE_HEX:
newRepresentation = REPRESENTATION_BYTE_DEC;
break;
case REPRESENTATION_BYTE_DEC:
newRepresentation = REPRESENTATION_STRING;
break;
case REPRESENTATION_STRING:
// NOTE: valid reps should really be determined programmatically by rep size, but it's not worth handling that way yet
if (0 == (offsetData.getByteLength() % 8))
newRepresentation = REPRESENTATION_TIME;
else if (0 == (offsetData.getByteLength() % 4))
newRepresentation = REPRESENTATION_FLOAT_DEC;
else if (0 == (offsetData.getByteLength() % 2))
newRepresentation = REPRESENTATION_SHORT_HEX;
else newRepresentation = REPRESENTATION_BYTE_HEX;
break;
default:
displayError("Illegal representation <" + offsetData.getRepresentation() + ">", "Error");
return;
}
offsetData.setRepresentation(newRepresentation);
shouldRecalcColumnWidths = false;
try
{
fireTableStructureChanged();
}
finally
{
shouldRecalcColumnWidths = true;
}
fireTableDataChanged();
}
/**
* @param row
* @param column
*/
public void setRowAndColumnOnPopup(int row, int column)
{
this.lastRowOnPopup = row;
this.lastColumnOnPopup = column;
}
/**
* @param row
* @param column
*/
public boolean hasValidPopupMenuItems(int row, int column)
{
boolean canSplitOffsetData = canSplitOffsetData(row, column);
boolean canMergeOffsetData = canMergeOffsetData(row, column);
boolean canNameAndLockOffsetData = canNameAndLockOffsetData(row, column);
boolean canUnlockOffsetData = canUnlockOffsetData(row, column);
splitMenuItem.setEnabled(canSplitOffsetData);
mergeMenuItem.setEnabled(canMergeOffsetData);
nameAndLockOffsetDataMenuItem.setEnabled(canNameAndLockOffsetData);
unlockOffsetDataMenuItem.setEnabled(canUnlockOffsetData);
return canSplitOffsetData | canMergeOffsetData | canNameAndLockOffsetData | canUnlockOffsetData;
}
private boolean canNameAndLockOffsetData(int row, int column)
{
int dataRowIndex = row - 2;
int dataColumnIndex = column - 1;
// UI Check
if (dataColumnIndex < 0) return false;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
// UI Check
if (offsetData.getByteLength() == offsetInOffsetData) return false; // EMPTY_COLUMN
return offsetData.canNameAndLock();
}
public void nameAndLockOffsetData()
{
int dataRowIndex = lastRowOnPopup - 2;
int dataColumnIndex = lastColumnOnPopup - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
if (false == offsetData.canNameAndLock()) return;
String newOffsetDataName = displayInputPanel("Enter Column Name:", "Name Offset Data");
if ((newOffsetDataName == null) || (newOffsetDataName.length() == 0))
return;
offsetData.nameAndLock(newOffsetDataName);
fireTableDataChanged();
}
private boolean canUnlockOffsetData(int row, int column)
{
int dataRowIndex = row - 2;
int dataColumnIndex = column - 1;
// UI Check
if (dataColumnIndex < 0) return false;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
// UI Check
if (offsetData.getByteLength() == offsetInOffsetData) return false; // EMPTY_COLUMN
return offsetData.canUnlock();
}
public void unlockOffsetData()
{
int dataRowIndex = lastRowOnPopup - 2;
int dataColumnIndex = lastColumnOnPopup - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
if (false == offsetData.canUnlock()) return;
offsetData.unlock();
fireTableDataChanged();
}
private boolean canSplitOffsetData(int row, int column)
{
int dataRowIndex = row - 2;
int dataColumnIndex = column - 1;
// UI Check
if (dataColumnIndex < 0) return false;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
// UI Check
if (offsetData.getByteLength() == offsetInOffsetData) return false; // EMPTY_COLUMN
return offsetData.canSplit(offsetInOffsetData);
}
public void splitOffsetData()
{
int dataRowIndex = lastRowOnPopup - 2;
int dataColumnIndex = lastColumnOnPopup - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
if (false == offsetData.canSplit(offsetInOffsetData)) return;
int offsetIndex = offsetList.indexOf(offsetData);
OffsetData newOffsetData = offsetData.split(offsetInOffsetData);
offsetList.add(offsetIndex + 1, newOffsetData);
shouldRecalcColumnWidths = false;
try
{
fireTableStructureChanged();
}
finally
{
shouldRecalcColumnWidths = true;
}
fireTableDataChanged();
}
private boolean canMergeOffsetData(int row, int column)
{
int dataRowIndex = lastRowOnPopup - 2;
int dataColumnIndex = lastColumnOnPopup - 1;
// UI Check
if (dataColumnIndex < 0) return false;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
// UI Check
if (offsetData.getByteLength() != offsetInOffsetData) return false; // not EMPTY_COLUMN
int offsetIndex = offsetList.indexOf(offsetData);
if ((offsetIndex + 1) == offsetList.size()) return false; // no offsets after this one
OffsetData nextOffsetData = (OffsetData)offsetList.get(offsetIndex + 1);
return offsetData.canMerge(nextOffsetData);
}
public void mergeOffsetData()
{
int dataRowIndex = lastRowOnPopup - 2;
int dataColumnIndex = lastColumnOnPopup - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
int offsetIndex = offsetList.indexOf(offsetData);
if ((offsetIndex + 1) == offsetList.size()) return; // no offsets after this one
OffsetData nextOffsetData = (OffsetData)offsetList.get(offsetIndex + 1);
if (false == offsetData.canMerge(nextOffsetData)) return;
if (offsetData.merge(nextOffsetData))
{
offsetList.remove(offsetIndex + 1);
}
shouldRecalcColumnWidths = false;
try
{
fireTableStructureChanged();
}
finally
{
shouldRecalcColumnWidths = true;
}
fireTableDataChanged();
}
public String getCellTooltip(int realRowIndex, int realColumnIndex)
{
if (0 == realColumnIndex) return null;
if (0 == realRowIndex) return "Click to change offset representation";
if (1 == realRowIndex) return "Click to change data representation";
int dataRowIndex = realRowIndex - 2;
int dataColumnIndex = realColumnIndex - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
if (offsetData.getByteLength() == offsetInOffsetData) return null; // EMPTY_COLUMN
return byteDataTableModel.getColumnHeader1(realColumnIndex) + " " + byteDataTableModel.getColumnHeader2(realColumnIndex) + ", " + byteDataTableModel.getDataRowName(dataRowIndex);
}
public Object getValueAt(int realRowIndex, int realColumnIndex)
{
if (0 == realRowIndex) return getColumnHeader1(realColumnIndex);
if (1 == realRowIndex) return getColumnHeader2(realColumnIndex);
int dataRowIndex = realRowIndex - 2;
int dataColumnIndex = realColumnIndex - 1;
if (0 == realColumnIndex) return getDataRowName(dataRowIndex);
return getDataValueAt(dataRowIndex, dataColumnIndex);
}
public Object getDataValueAt(int dataRowIndex, int dataColumnIndex)
{
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
if (offsetData.getByteLength() == offsetInOffsetData) return null; // EMPTY_COLUMN
// System.out.println("dataRowIndex=" + dataRowIndex + ", dataColumnIndex=" + dataColumnIndex + ", datumColumns=" + datumColumns + ", dataIndex=" + dataIndex + ", dataType=" + dataType);
int offset = offsetData.getOffset() + offsetInOffsetData;
switch (offsetData.getRepresentation())
{
case REPRESENTATION_TIME:
return DateTime.toString(internalModelDataSource.getRowDataAsLongAtPosition(dataRowIndex, offset));
case REPRESENTATION_FLOAT_DEC:
return Float.toString(internalModelDataSource.getRowDataAsFloatAtPosition(dataRowIndex, offset));
case REPRESENTATION_INT_HEX:
return Long.toHexString(internalModelDataSource.getRowDataAsUnsignedIntegerAtPosition(dataRowIndex, offset));
case REPRESENTATION_INT_DEC:
return String.valueOf(internalModelDataSource.getRowDataAsUnsignedIntegerAtPosition(dataRowIndex, offset));
case REPRESENTATION_SHORT_HEX:
return Integer.toHexString(internalModelDataSource.getRowDataAsUnsignedShortAtPosition(dataRowIndex, offset));
case REPRESENTATION_SHORT_DEC:
return String.valueOf(internalModelDataSource.getRowDataAsSignedShortAtPosition(dataRowIndex, offset));
case REPRESENTATION_BYTE_HEX:
return Integer.toHexString(internalModelDataSource.getRowDataAsUnsignedByteAtPosition(dataRowIndex, offset));
case REPRESENTATION_BYTE_DEC:
return String.valueOf(internalModelDataSource.getRowDataAsUnsignedByteAtPosition(dataRowIndex, offset));
case REPRESENTATION_STRING:
return internalModelDataSource.getRowDataAsZeroTerminatedStringAtPosition(dataRowIndex, offset, offsetData.getByteLength());
default:
displayError("Illegal representation <" + offsetData.getRepresentation() + ">", "Error");
return null;
}
}
public boolean isCellEditable(int realRow, int realColumn)
{
if (0 == realColumn) return false;
if ((0 == realRow) || (1 == realRow)) return false;
int dataRowIndex = realRow - 2;
int dataColumnIndex = realColumn - 1;
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
if (offsetData.getByteLength() == offsetInOffsetData) return false; // EMPTY_COLUMN
return true;
}
public void setValueAt(Object value, int realRow, int realColumn)
{
if ((0 == realRow) || (1 == realRow))
{
displayError("Illegal row,column pair [" + realRow + "," + realColumn + "]", "Error");
return;
}
if (0 == realColumn)
{
displayError("Illegal row,column pair [" + realRow + "," + realColumn + "]", "Error");
return;
}
int dataRow = realRow - 2;
int dataColumn = realColumn - 1;
Object oldValue = getDataValueAt(dataRow, dataColumn);
setDataValueAt(value, dataRow, dataColumn);
Object newValue = getDataValueAt(dataRow, dataColumn);
if ((null == oldValue) && (null != newValue))
{
fireTableCellUpdated(realRow, realColumn);
}
else if (false == oldValue.equals(newValue))
{
fireTableCellUpdated(realRow, realColumn);
}
}
public void setDataValueAt(Object value, int dataRowIndex, int dataColumnIndex)
{
OffsetData offsetData = getOffsetDataForColumn(dataColumnIndex);
int offsetInOffsetData = getOffsetInOffsetDataForColumn(dataColumnIndex);
if (offsetData.getByteLength() == offsetInOffsetData) // EMPTY_COLUMN
{
displayError("Illegal row,column pair [" + dataRowIndex + "," + dataColumnIndex + "]", "Error");
return;
}
int offset = offsetData.getOffset() + offsetInOffsetData;
switch (offsetData.getRepresentation())
{
case REPRESENTATION_TIME:
Long result = DateTime.parseLong((String)value);
if (null == result)
{
displayError("Unable to convert <" + value + "> to game time.", "Error");
return;
}
internalModelDataSource.setRowDataAsLongAtPosition(dataRowIndex, result.longValue(), offset);
break;
case REPRESENTATION_FLOAT_DEC:
internalModelDataSource.setRowDataAsFloatAtPosition(dataRowIndex, Long.parseLong((String)value, 16), offset);
break;
case REPRESENTATION_INT_HEX:
internalModelDataSource.setRowDataAsUnsignedIntegerAtPosition(dataRowIndex, Long.parseLong((String)value, 16), offset);
break;
case REPRESENTATION_INT_DEC:
internalModelDataSource.setRowDataAsUnsignedIntegerAtPosition(dataRowIndex, Long.parseLong((String)value), offset);
break;
case REPRESENTATION_SHORT_HEX:
internalModelDataSource.setRowDataAsSignedShortAtPosition(dataRowIndex, Short.parseShort((String)value, 16), offset);
break;
case REPRESENTATION_SHORT_DEC:
internalModelDataSource.setRowDataAsSignedShortAtPosition(dataRowIndex, Short.parseShort((String)value), offset);
break;
case REPRESENTATION_BYTE_HEX:
internalModelDataSource.setRowDataAsUnsignedByteAtPosition(dataRowIndex, Short.parseShort((String)value, 16), offset);
break;
case REPRESENTATION_BYTE_DEC:
internalModelDataSource.setRowDataAsUnsignedByteAtPosition(dataRowIndex, Short.parseShort((String)value), offset);
break;
case REPRESENTATION_STRING:
internalModelDataSource.setRowDataAsZeroTerminatedStringAtPosition(dataRowIndex, (String)value, offset, offsetData.getByteLength());
break;
default:
displayError("Illegal representation <" + offsetData.getRepresentation() + ">", "Error");
return;
}
}
public String exportDataAsCSV()
{
String dataString = "";
for (int row = 0; row < getRowCount(); ++row)
{
for (int col = 0; col < getColumnCount(); ++col)
{
Object value = getValueAt(row, col);
if (value != null) dataString += String.valueOf(value);
dataString += "\t";
}
dataString += "\n";
}
return dataString;
}
public boolean shouldRecalcColumnWidths()
{
return shouldRecalcColumnWidths;
}
}
}