Files
EMARS/src/main/java/mars/venus/TextSegmentWindow.java
T
2022-11-12 17:06:02 -05:00

1316 lines
52 KiB
Java

package mars.venus;
import mars.Globals;
import mars.ProgramStatement;
import mars.Settings;
import mars.mips.hardware.*;
import mars.simulator.Simulator;
import mars.simulator.SimulatorNotice;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.*;
/*
Copyright (c) 2003-2007, Pete Sanderson and Kenneth Vollmar
Developed by Pete Sanderson (psanderson@otterbein.edu)
and Kenneth Vollmar (kenvollmar@missouristate.edu)
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.
(MIT license, http://www.opensource.org/licenses/mit-license.html)
*/
/**
* Creates the Text Segment window in the Execute tab of the UI
*
* @author Team JSpim
**/
public class TextSegmentWindow extends JInternalFrame implements Observer
{
private static final int PROGRAM_ARGUMENT_TEXTFIELD_COLUMNS = 40;
private static final int BREAK_COLUMN = 0;
private static final int ADDRESS_COLUMN = 1;
private static final int CODE_COLUMN = 2;
private static final int BASIC_COLUMN = 3;
private static final int SOURCE_COLUMN = 4;
private static final Font monospacedPlain12Point = new Font("Monospaced", Font.PLAIN, 12);
// The following is displayed in the Basic and Source columns if existing code is overwritten using self-modifying code feature
private static final String modifiedCodeMarker = " ------ ";
private static final String[] columnNames = {"Bkpt", "Address", "Code", "Basic", "Source"};
private final JPanel programArgumentsPanel; // DPS 17-July-2008
private final JTextField programArgumentsTextField; // DPS 17-July-2008
private JTable table;
private JScrollPane tableScroller;
private Object[][] data;
/* Maintain an int array of code addresses in parallel with ADDRESS_COLUMN,
* to speed model-row -> text-address mapping. Maintain a Hashtable of
* (text-address, model-row) pairs to speed text-address -> model-row mapping.
* The former is used for breakpoints and changing display base (e.g. base 10
* to 16); the latter is used for highlighting. Both structures will remain
* consistent once set up, since address column is not editable.
*/
private int[] intAddresses; // index is table model row, value is text address
private Hashtable addressRows; // key is text address, value is table model row
private Hashtable<Integer, ModifiedCode> executeMods; // key is table model row, value is original code, basic, source.
private final Container contentPane;
private TextTableModel tableModel;
private final Font tableCellFont = new Font("Monospaced", Font.PLAIN, 12);
private boolean codeHighlighting;
private boolean breakpointsEnabled; // Added 31 Dec 2009
private int highlightAddress;
private TableModelListener tableModelListener;
private boolean inDelaySlot; // Added 25 June 2007
/**
* Constructor, sets up a new JInternalFrame.
**/
public TextSegmentWindow()
{
super("Text Segment", true, false, true, true);
Simulator.getInstance().addObserver(this);
Globals.getSettings().addObserver(this);
contentPane = this.getContentPane();
codeHighlighting = true;
breakpointsEnabled = true;
programArgumentsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
programArgumentsPanel.add(new JLabel("Program Arguments: "));
programArgumentsTextField = new JTextField(PROGRAM_ARGUMENT_TEXTFIELD_COLUMNS);
programArgumentsTextField.setToolTipText("Arguments provided to program at runtime via $a0 (argc) and $a1 (argv)");
programArgumentsPanel.add(programArgumentsTextField);
}
/**
* Method to be called once the user compiles the program. Should convert the lines of code over to the table rows
* and columns.
**/
public void setupTable()
{
int addressBase = Globals.getGui().getMainPane().getExecutePane().getAddressDisplayBase();
codeHighlighting = true;
breakpointsEnabled = true;
ArrayList sourceStatementList = Globals.program.getMachineList();
data = new Object[sourceStatementList.size()][columnNames.length];
intAddresses = new int[data.length];
addressRows = new Hashtable(data.length);
executeMods = new Hashtable<Integer, ModifiedCode>(data.length);
// Get highest source line number to determine #leading spaces so line numbers will vertically align
// In multi-file situation, this will not necessarily be the last line b/c sourceStatementList contains
// source lines from all files. DPS 3-Oct-10
int maxSourceLineNumber = 0;
for (int i = sourceStatementList.size() - 1; i >= 0; i--)
{
ProgramStatement statement = (ProgramStatement) sourceStatementList.get(i);
if (statement.getSourceLine() > maxSourceLineNumber)
{
maxSourceLineNumber = statement.getSourceLine();
}
}
int sourceLineDigits = ("" + maxSourceLineNumber).length();
int leadingSpaces = 0;
int lastLine = -1;
for (int i = 0; i < sourceStatementList.size(); i++)
{
ProgramStatement statement = (ProgramStatement) sourceStatementList.get(i);
intAddresses[i] = statement.getAddress();
addressRows.put(Integer.valueOf(intAddresses[i]), Integer.valueOf(i));
data[i][BREAK_COLUMN] = Boolean.FALSE;
data[i][ADDRESS_COLUMN] = NumberDisplayBaseChooser.formatUnsignedInteger(statement.getAddress(), addressBase);
data[i][CODE_COLUMN] = NumberDisplayBaseChooser.formatNumber(statement.getBinaryStatement(), 16);
data[i][BASIC_COLUMN] = statement.getPrintableBasicAssemblyStatement();
String sourceString = "";
if (!statement.getSource().equals(""))
{
leadingSpaces = sourceLineDigits - ("" + statement.getSourceLine()).length();
String lineNumber = " ".substring(0, leadingSpaces)
+ statement.getSourceLine() + ": ";
if (statement.getSourceLine() == lastLine)
{
lineNumber = " ".substring(0, sourceLineDigits) + " ";
}
sourceString = lineNumber
+ mars.util.EditorFont.substituteSpacesForTabs(statement.getSource());
}
data[i][SOURCE_COLUMN] = sourceString;
lastLine = statement.getSourceLine();
}
contentPane.removeAll();
tableModel = new TextTableModel(data);
if (tableModelListener != null)
{
tableModel.addTableModelListener(tableModelListener);
tableModel.fireTableDataChanged();// initialize listener
}
table = new MyTippedJTable(tableModel);
// prevents cells in row from being highlighted when user clicks on breakpoint checkbox
table.setRowSelectionAllowed(false);
table.getColumnModel().getColumn(BREAK_COLUMN).setMinWidth(40);
table.getColumnModel().getColumn(ADDRESS_COLUMN).setMinWidth(80);
table.getColumnModel().getColumn(CODE_COLUMN).setMinWidth(80);
table.getColumnModel().getColumn(BREAK_COLUMN).setMaxWidth(50);
table.getColumnModel().getColumn(ADDRESS_COLUMN).setMaxWidth(90);
table.getColumnModel().getColumn(CODE_COLUMN).setMaxWidth(90);
table.getColumnModel().getColumn(BASIC_COLUMN).setMaxWidth(200);
table.getColumnModel().getColumn(BREAK_COLUMN).setPreferredWidth(40);
table.getColumnModel().getColumn(ADDRESS_COLUMN).setPreferredWidth(80);
table.getColumnModel().getColumn(CODE_COLUMN).setPreferredWidth(80);
table.getColumnModel().getColumn(BASIC_COLUMN).setPreferredWidth(160);
table.getColumnModel().getColumn(SOURCE_COLUMN).setPreferredWidth(280);
CodeCellRenderer codeStepHighlighter = new CodeCellRenderer();
table.getColumnModel().getColumn(BASIC_COLUMN).setCellRenderer(codeStepHighlighter);
table.getColumnModel().getColumn(SOURCE_COLUMN).setCellRenderer(codeStepHighlighter);
// to render String right-justified in mono font
table.getColumnModel().getColumn(ADDRESS_COLUMN).setCellRenderer(new MonoRightCellRenderer());
table.getColumnModel().getColumn(CODE_COLUMN).setCellRenderer(new MachineCodeCellRenderer());
table.getColumnModel().getColumn(BREAK_COLUMN).setCellRenderer(new CheckBoxTableCellRenderer());
reorderColumns(); // Re-order columns according to current preference...
// Add listener to catch column re-ordering for updating settings.
table.getColumnModel().addColumnModelListener(new MyTableColumnMovingListener());
tableScroller = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
contentPane.add(tableScroller);
if (Globals.getSettings().getProgramArguments())
{
addProgramArgumentsPanel();
}
deleteAsTextSegmentObserver();
if (Globals.getSettings().getBooleanSetting(Settings.SELF_MODIFYING_CODE_ENABLED))
{
addAsTextSegmentObserver();
}
}
//////////// Support for program arguments added DPS 17-July-2008 //////////////
/**
* Get program arguments from text field in south border of text segment window.
*
* @return String containing program arguments
*/
public String getProgramArguments()
{
return programArgumentsTextField.getText();
}
public void addProgramArgumentsPanel()
{
// Don't add it if text segment window blank (file closed or no assemble yet)
if (contentPane != null && contentPane.getComponentCount() > 0)
{
contentPane.add(programArgumentsPanel, BorderLayout.NORTH);
contentPane.validate();
}
}
public void removeProgramArgumentsPanel()
{
if (contentPane != null)
{
contentPane.remove(programArgumentsPanel);
contentPane.validate();
}
}
//
///////////////////////// end program arguments section ////////////////////////
/**
* remove all components
*/
public void clearWindow()
{
contentPane.removeAll();
}
/**
* Assign listener to Table model. Used for breakpoints, since that is the only editable column in the table.
* Since table model objects are transient (get a new one with each successful assemble), this method will simply
* keep the identity of the listener then add it as a listener each time a new table model object is created. Limit
* 1 listener.
*/
public void registerTableModelListener(TableModelListener tml)
{
tableModelListener = tml;
}
/**
* Redisplay the addresses. This should only be done when address display base is modified (e.g. between base 16
* hex and base 10 dec).
*/
public void updateCodeAddresses()
{
if (contentPane.getComponentCount() == 0)
{
return; // ignore if no content to change
}
int addressBase = Globals.getGui().getMainPane().getExecutePane().getAddressDisplayBase();
int address;
String formattedAddress;
for (int i = 0; i < intAddresses.length; i++)
{
formattedAddress = NumberDisplayBaseChooser.formatUnsignedInteger(intAddresses[i], addressBase);
table.getModel().setValueAt(formattedAddress, i, ADDRESS_COLUMN);
}
}
/**
* Redisplay the basic statements. This should only be done when address or value display base is modified (e.g.
* between base 16 hex and base 10 dec).
*/
public void updateBasicStatements()
{
if (contentPane.getComponentCount() == 0)
{
return; // ignore if no content to change
}
ArrayList sourceStatementList = Globals.program.getMachineList();
for (int i = 0; i < sourceStatementList.size(); i++)
{
// Loop has been extended to cover self-modifying code. If code at this memory location has been
// modified at runtime, construct a ProgramStatement from the current address and binary code
// then display its basic code. DPS 11-July-2013
if (executeMods.get(i) == null)
{ // not modified, so use original logic.
ProgramStatement statement = (ProgramStatement) sourceStatementList.get(i);
table.getModel().setValueAt(statement.getPrintableBasicAssemblyStatement(), i, BASIC_COLUMN);
}
else
{
try
{
ProgramStatement statement = new ProgramStatement(
mars.util.Binary.stringToInt((String) table.getModel().getValueAt(i, CODE_COLUMN)),
mars.util.Binary.stringToInt((String) table.getModel().getValueAt(i, ADDRESS_COLUMN))
);
table.getModel().setValueAt(statement.getPrintableBasicAssemblyStatement(), i, BASIC_COLUMN);
}
catch (NumberFormatException e)
{ // should never happen but just in case...
table.getModel().setValueAt("", i, BASIC_COLUMN);
}
}
}
}
/**
* Required by Observer interface. Called when notified by an Observable that we are registered with. The
* Observable here is a delegate of the Memory object, which lets us know of memory operations. More precisely,
* memory operations only in the text segment, since that is the only range of addresses we're registered for. And
* we're only interested in write operations.
*
* @param observable The Observable object who is notifying us
* @param obj Auxiliary object with additional information.
*/
public void update(Observable observable, Object obj)
{
if (observable == mars.simulator.Simulator.getInstance())
{
SimulatorNotice notice = (SimulatorNotice) obj;
if (notice.getAction() == SimulatorNotice.SIMULATOR_START)
{
// Simulated MIPS execution starts. Respond to text segment changes only if self-modifying code
// enabled. I commented out conditions that would further limit it to running in timed or stepped mode.
// Seems reasonable for text segment display to be accurate in cases where existing code is overwritten
// even when running at unlimited speed. DPS 10-July-2013
deleteAsTextSegmentObserver();
if (Globals.getSettings().getBooleanSetting(Settings.SELF_MODIFYING_CODE_ENABLED))
{ // && (notice.getRunSpeed() != RunSpeedPanel.UNLIMITED_SPEED || notice.getMaxSteps()==1)) {
addAsTextSegmentObserver();
}
}
}
else if (observable == Globals.getSettings())
{
deleteAsTextSegmentObserver();
if (Globals.getSettings().getBooleanSetting(Settings.SELF_MODIFYING_CODE_ENABLED))
{
addAsTextSegmentObserver();
}
}
else if (obj instanceof MemoryAccessNotice)
{
// NOTE: observable != Memory.getInstance() because Memory class delegates notification duty.
// This will occur only if running program has written to text segment (self-modifying code)
MemoryAccessNotice access = (MemoryAccessNotice) obj;
if (access.getAccessType() == AccessNotice.WRITE)
{
int address = access.getAddress();
int value = access.getValue();
String strValue = mars.util.Binary.intToHexString(access.getValue());
String strBasic = modifiedCodeMarker;
String strSource = modifiedCodeMarker;
// Translate the address into table model row and modify the values in that row accordingly.
int row = 0;
try
{
row = findRowForAddress(address);
}
catch (IllegalArgumentException e)
{
return; // do nothing if address modified is outside the range of original program.
}
ModifiedCode mc = executeMods.get(row);
if (mc == null)
{ // if not already modified
// Not already modified and new code is same as original --> do nothing.
if (tableModel.getValueAt(row, CODE_COLUMN).equals(strValue))
{
return;
}
mc = new ModifiedCode(
row,
tableModel.getValueAt(row, CODE_COLUMN),
tableModel.getValueAt(row, BASIC_COLUMN),
tableModel.getValueAt(row, SOURCE_COLUMN)
);
executeMods.put(row, mc);
// make a ProgramStatement and get basic code to display in BASIC_COLUMN
strBasic = new ProgramStatement(value, address).getPrintableBasicAssemblyStatement();
}
else
{
// If restored to original value, restore the basic and source
// This will be the case upon backstepping.
if (mc.getCode().equals(strValue))
{
strBasic = (String) mc.getBasic();
strSource = (String) mc.getSource();
// remove from executeMods since we are back to original
executeMods.remove(row);
}
else
{
// make a ProgramStatement and get basic code to display in BASIC_COLUMN
strBasic = new ProgramStatement(value, address).getPrintableBasicAssemblyStatement();
}
}
// For the code column, we don't want to do the following:
// tableModel.setValueAt(strValue, row, CODE_COLUMN)
// because that method will write to memory using Memory.setRawWord() which will
// trigger notification to observers, which brings us back to here!!! Infinite
// indirect recursion results. Neither fun nor productive. So what happens is
// this: (1) change to memory cell causes setValueAt() to be automatically be
// called. (2) it updates the memory cell which in turn notifies us which invokes
// the update() method - the method we're in right now. All we need to do here is
// update the table model then notify the controller/view to update its display.
data[row][CODE_COLUMN] = strValue;
tableModel.fireTableCellUpdated(row, CODE_COLUMN);
// The other columns do not present a problem since they are not editable by user.
tableModel.setValueAt(strBasic, row, BASIC_COLUMN);
tableModel.setValueAt(strSource, row, SOURCE_COLUMN);
// Let's update the value displayed in the DataSegmentWindow too. But it only observes memory while
// the MIPS program is running, and even then only in timed or step mode. There are good reasons
// for that. So we'll pretend to be Memory observable and send it a fake memory write update.
try
{
Globals.getGui().getMainPane().getExecutePane().getDataSegmentWindow()
.update(Memory.getInstance(), new MemoryAccessNotice(AccessNotice.WRITE, address, value));
}
catch (Exception e)
{
// Not sure if anything bad can happen in this sequence, but if anything does we can let it go.
}
}
}
}
/**
* Called by RunResetAction to restore display of any table rows that were overwritten due to self-modifying code
* feature.
*/
void resetModifiedSourceCode()
{
if (executeMods != null && !executeMods.isEmpty())
{
for (Enumeration<ModifiedCode> elements = executeMods.elements(); elements.hasMoreElements(); )
{
ModifiedCode mc = elements.nextElement();
tableModel.setValueAt(mc.getCode(), mc.getRow(), CODE_COLUMN);
tableModel.setValueAt(mc.getBasic(), mc.getRow(), BASIC_COLUMN);
tableModel.setValueAt(mc.getSource(), mc.getRow(), SOURCE_COLUMN);
}
executeMods.clear();
}
}
/**
* Return code address as an int, for the specified row of the table. This should only be used by the code renderer
* so I will not verify row.
*/
int getIntCodeAddressAtRow(int row)
{
return intAddresses[row];
}
/**
* Returns number of breakpoints currently set.
*
* @return number of current breakpoints
*/
public int getBreakpointCount()
{
int breakpointCount = 0;
for (int i = 0; i < data.length; i++)
{
if (((Boolean) data[i][BREAK_COLUMN]).booleanValue())
{
breakpointCount++;
}
}
return breakpointCount;
}
/**
* Returns array of current breakpoints, each represented by a MIPS program counter address. These are stored in the
* BREAK_COLUMN of the table model.
*
* @return int array of breakpoints, sorted by PC address, or null if there are none.
*/
public int[] getSortedBreakPointsArray()
{
int breakpointCount = getBreakpointCount();
if (breakpointCount == 0 || !breakpointsEnabled)
{ // added second condition 31-dec-09 DPS
return null;
}
int[] breakpoints = new int[breakpointCount];
breakpointCount = 0;
for (int i = 0; i < data.length; i++)
{
if (((Boolean) data[i][BREAK_COLUMN]).booleanValue())
{
breakpoints[breakpointCount++] = intAddresses[i];
}
}
Arrays.sort(breakpoints);
return breakpoints;
}
/**
* Clears all breakpoints that have been set since last assemble, and updates the display of the breakpoint column.
*/
public void clearAllBreakpoints()
{
for (int i = 0; i < tableModel.getRowCount(); i++)
{
if (((Boolean) data[i][BREAK_COLUMN]).booleanValue())
{
// must use this method to assure display updated and listener notified
tableModel.setValueAt(Boolean.FALSE, i, BREAK_COLUMN);
}
}
// Handles an obscure situation: if you click to set some breakpoints then "immediately" clear them
// all using the shortcut (CTRL-K), the last checkmark set is not removed even though the breakpoint
// is removed (tableModel.setValueAt(Boolean.FALSE, i, BREAK_COLUMN)) and all the other checkmarks
// are removed. The checkmark remains although if you subsequently run the program it will blow
// through because the data model cell really has been cleared (contains false). Occurs only when
// the last checked breakpoint check box still has the "focus". There is but one renderer and editor
// per column. Getting the renderer and setting it "setSelected(false)" will not work. You have
// to get the editor instead. (PS, 7 Aug 2006)
((JCheckBox) ((DefaultCellEditor) table.getCellEditor(0, BREAK_COLUMN)).getComponent()).setSelected(false);
}
/**
* Highlights the source code line whose address matches the current program counter value. This is used for
* stepping through code execution and when reaching breakpoints.
*/
public void highlightStepAtPC()
{
highlightStepAtAddress(RegisterFile.getPc(), false);
}
/**
* Highlights the source code line whose address matches the current program counter value. This is used for
* stepping through code execution and when reaching breakpoints.
*
* @param inDelaySlot Set true if delayed branching is enabled and the instruction at this address is executing
* in the delay slot, false otherwise.
*/
public void highlightStepAtPC(boolean inDelaySlot)
{
highlightStepAtAddress(RegisterFile.getPc(), inDelaySlot);
}
/**
* Highlights the source code line whose address matches the given text segment address.
*
* @param address text segment address of instruction to be highlighted.
*/
public void highlightStepAtAddress(int address)
{
highlightStepAtAddress(address, false);
}
/**
* Highlights the source code line whose address matches the given text segment address.
*
* @param address Text segment address of instruction to be highlighted.
* @param inDelaySlot Set true if delayed branching is enabled and the instruction at this address is executing
* in the delay slot, false otherwise.
*/
public void highlightStepAtAddress(int address, boolean inDelaySlot)
{
highlightAddress = address;
// Scroll if necessary to assure highlighted row is visible.
int row = 0;
try
{
row = findRowForAddress(address);
}
catch (IllegalArgumentException e)
{
return;
}
table.scrollRectToVisible(table.getCellRect(row, 0, true));
this.inDelaySlot = inDelaySlot;// Added 25 June 2007
// Trigger highlighting, which is done by the column's cell renderer.
// IMPLEMENTATION NOTE: Pretty crude implementation; mark all rows
// as changed so assure that the previously highlighted row is
// unhighlighted. Would be better to keep track of previous row
// then fire two events: one for it and one for the new row.
table.tableChanged(new TableModelEvent(tableModel));
//this.inDelaySlot = false;// Added 25 June 2007
}
/**
* Get code highlighting status.
*
* @return true if code highlighting currently enabled, false otherwise.
*/
public boolean getCodeHighlighting()
{
return codeHighlighting;
}
/**
* Used to enable or disable source code highlighting. If true (normally while stepping through execution) then
* MIPS statement at current program counter is highlighted. The code column's cell renderer tests this variable.
*
* @param highlightSetting true to enable highlighting, false to disable.
*/
public void setCodeHighlighting(boolean highlightSetting)
{
codeHighlighting = highlightSetting;
}
/**
* If any steps are highlighted, this erases the highlighting.
*/
public void unhighlightAllSteps()
{
boolean saved = this.getCodeHighlighting();
this.setCodeHighlighting(false);
table.tableChanged(new TableModelEvent(tableModel, 0, data.length - 1, BASIC_COLUMN));
table.tableChanged(new TableModelEvent(tableModel, 0, data.length - 1, SOURCE_COLUMN));
this.setCodeHighlighting(saved);
}
/**
* Scroll the viewport so the step (table row) at the given text segment address is visible, vertically centered if
* possible, and selected. Developed July 2007 for new feature that shows source code step where label is defined
* when that label is clicked on in the Label Window.
*
* @param address text segment address of source code step.
*/
void selectStepAtAddress(int address)
{
int addressRow = 0;
try
{
addressRow = findRowForAddress(address);
}
catch (IllegalArgumentException e)
{
return;
}
// Scroll to assure desired row is centered in view port.
int addressSourceColumn = table.convertColumnIndexToView(SOURCE_COLUMN);
Rectangle sourceCell = table.getCellRect(addressRow, addressSourceColumn, true);
double cellHeight = sourceCell.getHeight();
double viewHeight = tableScroller.getViewport().getExtentSize().getHeight();
int numberOfVisibleRows = (int) (viewHeight / cellHeight);
int newViewPositionY = Math.max((int) ((addressRow - (numberOfVisibleRows / 2)) * cellHeight), 0);
tableScroller.getViewport().setViewPosition(new Point(0, newViewPositionY));
// Select the source code cell for this row by generating a fake Mouse Pressed event
// and explicitly invoking the table's mouse listener.
MouseEvent fakeMouseEvent = new MouseEvent(table, MouseEvent.MOUSE_PRESSED,
new Date().getTime(), MouseEvent.BUTTON1_MASK,
(int) sourceCell.getX() + 1,
(int) sourceCell.getY() + 1, 1, false);
MouseListener[] mouseListeners = table.getMouseListeners();
for (int i = 0; i < mouseListeners.length; i++)
{
mouseListeners[i].mousePressed(fakeMouseEvent);
}
}
/**
* Enable or disable all items in the Breakpoints column.
*/
public void toggleBreakpoints()
{
// Already programmed to toggle by clicking on column header, so we'll create
// a fake mouse event with coordinates on that header then generate the fake
// event on its mouse listener.
Rectangle rect = ((MyTippedJTable) table).getRectForColumnIndex(BREAK_COLUMN);
MouseEvent fakeMouseEvent = new MouseEvent(table, MouseEvent.MOUSE_CLICKED,
new Date().getTime(), MouseEvent.BUTTON1_MASK,
(int) rect.getX(), (int) rect.getY(), 1, false);
MouseListener[] mouseListeners = ((MyTippedJTable) table).tableHeader.getMouseListeners();
for (int i = 0; i < mouseListeners.length; i++)
{
mouseListeners[i].mouseClicked(fakeMouseEvent);
}
}
/*
* Little convenience method to add this as observer of text segment
*/
private void addAsTextSegmentObserver()
{
try
{
Memory.getInstance().addObserver(this, Memory.textBaseAddress, Memory.dataSegmentBaseAddress);
}
catch (AddressErrorException aee)
{
}
}
/*
* Little convenience method to remove this as observer of text segment
*/
private void deleteAsTextSegmentObserver()
{
Memory.getInstance().deleteObserver(this);
}
/*
* Re-order the Text segment columns according to saved preferences.
*/
private void reorderColumns()
{
TableColumnModel oldtcm = table.getColumnModel();
TableColumnModel newtcm = new DefaultTableColumnModel();
int[] savedColumnOrder = Globals.getSettings().getTextColumnOrder();
// Apply ordering only if correct number of columns.
if (savedColumnOrder.length == table.getColumnCount())
{
for (int i = 0; i < savedColumnOrder.length; i++)
{
newtcm.addColumn(oldtcm.getColumn(savedColumnOrder[i]));
}
table.setColumnModel(newtcm);
}
}
/*
* Helper method to find the table row corresponding to the given.
* text segment address. This method is called by
* a couple different public methods. Returns the table row
* corresponding to this address.
*/
private int findRowForAddress(int address) throws IllegalArgumentException
{
int addressRow = 0;
try
{
addressRow = ((Integer) addressRows.get(Integer.valueOf(address))).intValue();
}
catch (NullPointerException e)
{
throw new IllegalArgumentException(); // address not found in map
//return addressRow;// if address not in map, do nothing.
}
return addressRow;
}
/**
* Inner class to implement the Table model for this JTable.
*/
class TextTableModel extends AbstractTableModel
{
Object[][] data;
public TextTableModel(Object[][] d)
{
data = d;
}
public int getColumnCount()
{
return columnNames.length;
}
public int getRowCount()
{
return data.length;
}
public String getColumnName(int col)
{
return columnNames[col];
}
public Object getValueAt(int row, int col)
{
return data[row][col];
}
/*
* JTable uses this method to determine the default renderer/
* editor for each cell. If we didn't implement this method,
* then the break column would contain text ("true"/"false"),
* rather than a check box.
*/
public Class getColumnClass(int c)
{
return getValueAt(0, c).getClass();
}
/*
* Don't need to implement this method unless your table's
* editable. Only Column #1, the Breakpoint, can be edited.
*/
public boolean isCellEditable(int row, int col)
{
//Note that the data/cell address is constant,
//no matter where the cell appears onscreen.
return col == BREAK_COLUMN || (col == CODE_COLUMN && Globals.getSettings().getBooleanSetting(Settings.SELF_MODIFYING_CODE_ENABLED));
}
/**
* Set cell contents in the table model. Overrides inherited empty method. Straightforward process except for
* the Code column.
*/
@Override
public void setValueAt(Object value, int row, int col)
{
if (col != CODE_COLUMN)
{
data[row][col] = value;
fireTableCellUpdated(row, col);
return;
}
// Handle changes in the Code column.
int val = 0;
int address = 0;
if (value.equals(data[row][col]))
{
return;
}
try
{
val = mars.util.Binary.stringToInt((String) value);
}
catch (NumberFormatException nfe)
{
data[row][col] = "INVALID";
fireTableCellUpdated(row, col);
return;
}
// calculate address from row and column
try
{
address = mars.util.Binary.stringToInt((String) data[row][ADDRESS_COLUMN]);
}
catch (NumberFormatException nfe)
{
// can't really happen since memory addresses are completely under
// the control of my software.
}
// Assures that if changed during MIPS program execution, the update will
// occur only between MIPS instructions.
synchronized (Globals.memoryAndRegistersLock)
{
try
{
Globals.memory.setRawWord(address, val);
}
// somehow, user was able to display out-of-range address. Most likely to occur between
// stack base and Kernel.
catch (AddressErrorException aee)
{
return;
}
}// end synchronized block
}
private void printDebugData()
{
int numRows = getRowCount();
int numCols = getColumnCount();
for (int i = 0; i < numRows; i++)
{
System.out.print(" row " + i + ":");
for (int j = 0; j < numCols; j++)
{
System.out.print(" " + data[i][j]);
}
System.out.println();
}
System.out.println("--------------------------");
}
}
private class ModifiedCode
{
private final Integer row;
private final Object code;
private final Object basic;
private final Object source;
private ModifiedCode(Integer row, Object code, Object basic, Object source)
{
this.row = row;
this.code = code;
this.basic = basic;
this.source = source;
}
private Integer getRow()
{
return this.row;
}
private Object getCode()
{
return this.code;
}
private Object getBasic()
{
return this.basic;
}
private Object getSource()
{
return this.source;
}
}
/* a custom table cell renderer that we'll use to highlight the current line of
* source code when executing using Step or breakpoint.
*/
class CodeCellRenderer extends DefaultTableCellRenderer
{
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
Component cell = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
//cell.setFont(tableCellFont);
TextSegmentWindow textSegment = Globals.getGui().getMainPane().getExecutePane().getTextSegmentWindow();
Settings settings = Globals.getSettings();
boolean highlighting = textSegment.getCodeHighlighting();
if (highlighting && textSegment.getIntCodeAddressAtRow(row) == highlightAddress)
{
if (mars.simulator.Simulator.inDelaySlot() || textSegment.inDelaySlot)
{
cell.setBackground(settings.getColorSettingByPosition(Settings.TEXTSEGMENT_DELAYSLOT_HIGHLIGHT_BACKGROUND));
cell.setForeground(settings.getColorSettingByPosition(Settings.TEXTSEGMENT_DELAYSLOT_HIGHLIGHT_FOREGROUND));
cell.setFont(settings.getFontByPosition(Settings.TEXTSEGMENT_DELAYSLOT_HIGHLIGHT_FONT));
}
else
{
cell.setBackground(settings.getColorSettingByPosition(Settings.TEXTSEGMENT_HIGHLIGHT_BACKGROUND));
cell.setForeground(settings.getColorSettingByPosition(Settings.TEXTSEGMENT_HIGHLIGHT_FOREGROUND));
cell.setFont(settings.getFontByPosition(Settings.TEXTSEGMENT_HIGHLIGHT_FONT));
}
}
else if (row % 2 == 0)
{
cell.setBackground(settings.getColorSettingByPosition(Settings.EVEN_ROW_BACKGROUND));
cell.setForeground(settings.getColorSettingByPosition(Settings.EVEN_ROW_FOREGROUND));
cell.setFont(settings.getFontByPosition(Settings.EVEN_ROW_FONT));
}
else
{
cell.setBackground(settings.getColorSettingByPosition(Settings.ODD_ROW_BACKGROUND));
cell.setForeground(settings.getColorSettingByPosition(Settings.ODD_ROW_FOREGROUND));
cell.setFont(settings.getFontByPosition(Settings.ODD_ROW_FONT));
}
return cell;
}
}
/*
* Cell renderer for Machine Code column. Alternates background color by row but otherwise is
* same as MonoRightCellRenderer.
*/
class MachineCodeCellRenderer extends DefaultTableCellRenderer
{
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
JLabel cell = (JLabel) super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
cell.setFont(MonoRightCellRenderer.MONOSPACED_PLAIN_12POINT);
cell.setHorizontalAlignment(SwingConstants.RIGHT);
if (row % 2 == 0)
{
cell.setBackground(Globals.getSettings().getColorSettingByPosition(Settings.EVEN_ROW_BACKGROUND));
cell.setForeground(Globals.getSettings().getColorSettingByPosition(Settings.EVEN_ROW_FOREGROUND));
}
else
{
cell.setBackground(Globals.getSettings().getColorSettingByPosition(Settings.ODD_ROW_BACKGROUND));
cell.setForeground(Globals.getSettings().getColorSettingByPosition(Settings.ODD_ROW_FOREGROUND));
}
return cell;
}
}
/*
* Cell renderer for Breakpoint column. We can use this to enable/disable breakpoint checkboxes with
* a single action. This class blatantly copied/pasted from
* http://www.javakb.com/Uwe/Forum.aspx/java-gui/1451/Java-TableCellRenderer-for-a-boolean-checkbox-field
* Slightly customized. DPS 31-Dec-2009
*/
class CheckBoxTableCellRenderer extends JCheckBox implements TableCellRenderer
{
javax.swing.border.Border noFocusBorder;
javax.swing.border.Border focusBorder;
public CheckBoxTableCellRenderer()
{
super();
setContentAreaFilled(true);
setBorderPainted(true);
setHorizontalAlignment(SwingConstants.CENTER);
setVerticalAlignment(SwingConstants.CENTER);
/**********************************************
Use this if you want to add "instant" recognition of breakpoint changes
during simulation run. Currently, the simulator gets array of breakpoints
only when "Go" is selected. Thus the system does not respond to breakpoints
added/removed during unlimited/timed execution. In order for it to do so,
we need to be informed of such changes and the ItemListener below will do this.
Then the item listener needs to inform the SimThread object so it can request
a fresh breakpoint array. That would make SimThread an observer. Synchronization
will come into play in the SimThread class? It could get complicated, which
is why I'm dropping it for release 3.8. DPS 31-dec-2009
addItemListener(
new ItemListener(){
public void itemStateChanged(ItemEvent e) {
String what = "state changed";
if (e.getStateChange()==ItemEvent.SELECTED) what = "selected";
if (e.getStateChange()==ItemEvent.DESELECTED) what = "deselected";
System.out.println("Item "+what);
}});
For a different approach, see RunClearBreakpointsAction.java. This menu item registers
as a TableModelListener by calling the TextSegmentWindow's registerTableModelListener
method. Then it is notified when the table model changes, and this occurs whenever
the user clicks on a breakpoint checkbox! Using this approach, the SimThread registers
similarly. A "GUI guard" is not needed in SimThread because it extends SwingWorker and
thus is only invoked when the IDE is present (never when running MARS in command mode).
*****************************************************/
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected,
boolean hasFocus,
int row, int column)
{
if (table == null)
{
// ???
}
else
{
if (isSelected)
{
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
}
else
{
setForeground(table.getForeground());
setBackground(table.getBackground());
}
setEnabled(table.isEnabled() && breakpointsEnabled);
setComponentOrientation(table.getComponentOrientation());
if (hasFocus)
{
if (focusBorder == null)
{
focusBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
}
setBorder(focusBorder);
}
else
{
if (noFocusBorder == null)
{
if (focusBorder == null)
{
focusBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
}
if (focusBorder != null)
{
Insets n = focusBorder.getBorderInsets(this);
noFocusBorder = new javax.swing.border.EmptyBorder(n);
}
}
setBorder(noFocusBorder);
}
setSelected(Boolean.TRUE.equals(value));
}
return this;
}
}
///////////////////////////////////////////////////////////////////
//
// JTable subclass to provide custom tool tips for each of the
// text table column headers. From Sun's JTable tutorial.
// http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
//
private class MyTippedJTable extends JTable
{
private JTableHeader tableHeader;
private final String[] columnToolTips = {
/* break */ "If checked, will set an execution breakpoint. Click header to disable/enable breakpoints",
/* address */ "Text segment address of binary instruction code",
/* code */ "32-bit binary MIPS instruction",
/* basic */ "Basic assembler instruction",
/* source */ "Source code line"
};
MyTippedJTable(TextTableModel m)
{
super(m);
}
//Implement table header tool tips.
protected JTableHeader createDefaultTableHeader()
{
tableHeader = new TextTableHeader(columnModel);
return tableHeader;
/*
new JTableHeader(columnModel) {
public String getToolTipText(MouseEvent e) {
String tip = null;
java.awt.Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex = columnModel.getColumn(index).getModelIndex();
return columnToolTips[realIndex];
}
};
*/
}
// Given the model index of a column header, will return rectangle
// rectangle of displayed header (may be in different position due to
// column re-ordering).
public Rectangle getRectForColumnIndex(int realIndex)
{
for (int i = 0; i < columnModel.getColumnCount(); i++)
{
if (columnModel.getColumn(i).getModelIndex() == realIndex)
{
return tableHeader.getHeaderRect(i);
}
}
return tableHeader.getHeaderRect(realIndex);
}
////////////////////////////////////////////////////////////////
//
// Customized table header that will both display tool tip when
// mouse hovers over each column, and also enable/disable breakpoints
// when mouse is clicked on breakpoint column. Both are
// customized based on the column under the mouse.
private class TextTableHeader extends JTableHeader
{
public TextTableHeader(TableColumnModel cm)
{
super(cm);
this.addMouseListener(new TextTableHeaderMouseListener());
}
public String getToolTipText(MouseEvent e)
{
Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex = columnModel.getColumn(index).getModelIndex();
return columnToolTips[realIndex];
}
/////////////////////////////////////////////////////////////////////
// When user clicks on beakpoint column header, breakpoints are
// toggled (enabled/disabled). DPS 31-Dec-2009
private class TextTableHeaderMouseListener implements MouseListener
{
public void mouseClicked(MouseEvent e)
{
Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex = columnModel.getColumn(index).getModelIndex();
if (realIndex == BREAK_COLUMN)
{
JCheckBox check = ((JCheckBox) ((DefaultCellEditor) table.getCellEditor(0, index)).getComponent());
breakpointsEnabled = !breakpointsEnabled;
check.setEnabled(breakpointsEnabled);
table.tableChanged(new TableModelEvent(tableModel, 0, data.length - 1, BREAK_COLUMN));
}
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
}
}
}
/*
* Will capture movement of text columns. This info goes into persistent store.
*/
private class MyTableColumnMovingListener implements TableColumnModelListener
{
// Don't care about these events but no adapter provided so...
public void columnAdded(TableColumnModelEvent e)
{
}
public void columnRemoved(TableColumnModelEvent e)
{
}
public void columnMarginChanged(ChangeEvent e)
{
}
public void columnSelectionChanged(ListSelectionEvent e)
{
}
// When column moves, save the new column order.
public void columnMoved(TableColumnModelEvent e)
{
int[] columnOrder = new int[table.getColumnCount()];
for (int i = 0; i < columnOrder.length; i++)
{
columnOrder[i] = table.getColumnModel().getColumn(i).getModelIndex();
}
// If movement is slow, this event may fire multiple times w/o
// actually changing the column order. If new column order is
// same as previous, do not save changes to persistent store.
int[] oldOrder = Globals.getSettings().getTextColumnOrder();
for (int i = 0; i < columnOrder.length; i++)
{
if (oldOrder[i] != columnOrder[i])
{
Globals.getSettings().setTextColumnOrder(columnOrder);
break;
}
}
}
}
}