/********************************************************************************
* The contents of this file are subject to the GNU General Public License *
* (GPL) Version 2 or later (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at *
* http://www.gnu.org/copyleft/gpl.html *
* *
* Software distributed under the License is distributed on an "AS IS" basis, *
* without warranty of any kind, either expressed or implied. See the License *
* for the specific language governing rights and limitations under the *
* License. *
* *
* This file was originally developed as part of the software suite that *
* supports the book "The Elements of Computing Systems" by Nisan and Schocken, *
* MIT Press 2005. If you modify the contents of this file, please document and *
* mark your changes clearly, for the benefit of others. *
********************************************************************************/
package HackGUI;
import Hack.ComputerParts.ComputerPartEvent;
import Hack.ComputerParts.ComputerPartEventListener;
import Hack.ComputerParts.MemoryGUI;
import Hack.Events.ClearEvent;
import Hack.Events.ClearEventListener;
import Hack.Events.ErrorEvent;
import Hack.Events.ErrorEventListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.Vector;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
/**
* This class represents the GUI of a memory.
*/
public class MemoryComponent extends JPanel implements MemoryGUI {
private static final String FIND_GIF_RESOURCE = "resources/find.gif";
private static final String SMALLNEW_GIF_RESOURCE = "resources/smallnew.gif";
// The default number of visible rows.
protected static final int DEFAULT_VISIBLE_ROWS = 10;
/**
* The current format.
*/
public int dataFormat;
// A vector containing the listeners to this object.
private Vector listeners;
// A vector containing the clear listeners to this object.
private Vector clearListeners;
// A vector containing the error listeners to this object.
private Vector errorEventListeners;
// A vector containing the repaint listeners to this object.
private Vector changeListeners;
// The table representing the memory.
protected JTable memoryTable;
// The model of the table.
private MemoryTableModel tableModel = new MemoryTableModel();
// The values of this memory in a string representation.
protected String[] valuesStr;
// The values of this memory in a short representation.
protected short[] values;
// The addresses of this memory.
protected String[] addresses;
// Creating buttons and icons.
protected MouseOverJButton searchButton = new MouseOverJButton();
protected MouseOverJButton clearButton = new MouseOverJButton();
private ImageIcon searchIcon = new ImageIcon(MemoryComponent.class.getResource(FIND_GIF_RESOURCE));
private ImageIcon clearIcon = new ImageIcon(MemoryComponent.class.getResource(SMALLNEW_GIF_RESOURCE));
// The window of searching a specific location in memory.
private SearchMemoryWindow searchWindow;
// The scrollpane on which the table is placed.
protected JScrollPane scrollPane;
// A vector containing the highlighted rows.
protected Vector highlightIndex;
// The index of the flashed row.
protected int flashIndex = -1;
// The location of this component relative to its top level ancestor.
protected Point topLevelLocation;
// The name of this component.
protected JLabel nameLbl = new JLabel();
// A boolean field specifying if the user can enter values into the table.
protected boolean isEnabled = true;
// The null value of this component
protected short nullValue;
// A boolean field specifying if the null value should be activated or not.
protected boolean hideNullValue;
// The start and end row indices of the enabled region.
protected int startEnabling, endEnabling;
// If true, the disbaled region is shaded.
protected boolean grayDisabledRange;
/**
* Constructs a new MemoryComponent.
*/
public MemoryComponent() {
dataFormat = Format.DEC_FORMAT;
startEnabling = -1;
endEnabling = -1;
JTextField tf = new JTextField();
tf.setFont(Utilities.bigBoldValueFont);
tf.setBorder(null);
DefaultCellEditor editor = new DefaultCellEditor(tf);
listeners = new Vector();
clearListeners = new Vector();
errorEventListeners = new Vector();
changeListeners = new Vector();
highlightIndex = new Vector();
memoryTable = new JTable(getTableModel());
memoryTable.setDefaultRenderer(memoryTable.getColumnClass(0), getCellRenderer());
memoryTable.getColumnModel().getColumn(getValueColumnIndex()).setCellEditor(editor);
memoryTable.setTableHeader(null);
values = new short[0];
addresses = new String[0];
valuesStr = new String[0];
searchWindow = new SearchMemoryWindow(this, memoryTable);
jbInit();
}
/**
* Sets the null value of this component.
*/
public void setNullValue (short value, boolean hideNullValue) {
nullValue = value;
this.hideNullValue = hideNullValue;
}
/**
* Enables user input into the source.
*/
public void enableUserInput() {
isEnabled = true;
}
/**
* Disables user input into the source.
*/
public void disableUserInput() {
isEnabled = false;
}
/**
* Returns the index of the values column.
*/
protected int getValueColumnIndex() {
return 1;
}
/**
* Returns the table model of this component.
*/
protected TableModel getTableModel() {
return new MemoryTableModel();
}
/**
* Returns the cell renderer of this component.
*/
protected DefaultTableCellRenderer getCellRenderer() {
return new MemoryTableCellRenderer();
}
/**
* Sets the name of this component.
*/
public void setName(String name) {
nameLbl.setText(name);
}
/**
* Sets the location of this component relative to its top level ancestor.
*/
public void setTopLevelLocation(Component top) {
topLevelLocation = Utilities.getTopLevelLocation(top, memoryTable);
}
public void addListener(ComputerPartEventListener listener) {
listeners.addElement(listener);
}
public void removeListener(ComputerPartEventListener listener) {
listeners.removeElement(listener);
}
public void notifyListeners(int address, short value) {
ComputerPartEvent event = new ComputerPartEvent(this,address,value);
for (int i=0;i<listeners.size();i++) {
((ComputerPartEventListener)listeners.elementAt(i)).valueChanged(event);
}
}
public void notifyListeners() {
ComputerPartEvent event = new ComputerPartEvent(this);
for (int i=0;i<listeners.size();i++) {
((ComputerPartEventListener)listeners.elementAt(i)).guiGainedFocus();
}
}
public void addClearListener (ClearEventListener listener) {
clearListeners.addElement(listener);
}
public void removeClearListener (ClearEventListener listener) {
clearListeners.removeElement(listener);
}
public void notifyClearListeners() {
ClearEvent clearEvent = new ClearEvent(this);
for(int i=0; i<clearListeners.size();i++)
((ClearEventListener)clearListeners.elementAt(i)).clearRequested(clearEvent);
}
/**
* Registers the given ErrorEventListener as a listener to this GUI.
*/
public void addErrorListener(ErrorEventListener listener) {
errorEventListeners.addElement(listener);
}
/**
* Un-registers the given ErrorEventListener from being a listener to this GUI.
*/
public void removeErrorListener(ErrorEventListener listener) {
errorEventListeners.removeElement(listener);
}
/**
* Notifies all the ErrorEventListener on an error in this gui by
* creating an ErrorEvent (with the error message) and sending it
* using the errorOccured method to all the listeners.
*/
public void notifyErrorListeners(String errorMessage) {
ErrorEvent event = new ErrorEvent(this, errorMessage);
for (int i=0; i<errorEventListeners.size(); i++)
((ErrorEventListener)errorEventListeners.elementAt(i)).errorOccured(event);
}
/**
* Registers the given MemoryChangeListener as a listener to this GUI.
*/
public void addChangeListener(MemoryChangeListener listener) {
changeListeners.addElement(listener);
}
/**
* Un-registers the given MemoryChangeListener from being a listener to this GUI.
*/
public void removeChangeListener(MemoryChangeListener listener) {
changeListeners.removeElement(listener);
}
/**
* Notifies all the changeListeners on a need to repaint themselves.
*/
public void notifyRepaintListeners() {
for (int i=0;i<changeListeners.size();i++) {
((MemoryChangeListener)changeListeners.elementAt(i)).repaintChange();
}
}
/**
* Notifies all the changeListeners on a need to revalidate themselves.
*/
public void notifyRevalidateListeners() {
for (int i=0;i<changeListeners.size();i++) {
((MemoryChangeListener)changeListeners.elementAt(i)).revalidateChange();
}
}
/**
* Sets the memory contents with the given values array. (assumes that the
* length of the given array equals to the gui's size)
*/
public void setContents(short[] newValues) {
values = new short[newValues.length];
addresses = new String[newValues.length];
valuesStr = new String[newValues.length];
System.arraycopy(newValues, 0, values, 0, newValues.length);
for(int i=0;i<values.length;i++) {
addresses[i] = Format.translateValueToString((short)i, Format.DEC_FORMAT);
valuesStr[i] = translateValueToString(values[i]);
}
memoryTable.revalidate();
repaint();
notifyRevalidateListeners();
}
/**
* Updates the values of the table memory.
*/
protected void updateTable(short value, int row) {
values[row] = value;
valuesStr[row] = translateValueToString(value);
}
/**
* Sets the contents of the memory in the given index with the given value.
* (Assumes legal index - between 0 and getSize()-1).
*/
public void setValueAt(int index, short value) {
updateTable(value,index);
repaint();
notifyRepaintListeners();
}
/**
* Resets the contents of this MemoryComponent.
*/
public void reset() {
for(int i= 0; i<values.length;i++){
updateTable(nullValue, i);
}
repaint();
notifyRepaintListeners();
memoryTable.clearSelection();
hideFlash();
hideHighlight();
}
/**
* Hides all highlightes.
*/
public void hideHighlight() {
highlightIndex.removeAllElements();
repaint();
}
/**
* Highlights the value at the given index.
*/
public void highlight(int index) {
highlightIndex.addElement(new Integer(index));
repaint();
}
/**
* hides the existing flash.
*/
public void hideFlash () {
flashIndex = -1;
repaint();
}
/**
* flashes the value at the given index.
*/
public void flash (int index) {
flashIndex = index;
Utilities.tableCenterScroll(this, memoryTable, index);
}
/**
* Sets the enabled range of this segment.
* Any address outside this range will be disabled for user input.
* If gray is true, addresses outside the range will be gray colored.
*/
public void setEnabledRange(int start, int end, boolean gray) {
startEnabling = start;
endEnabling = end;
grayDisabledRange = gray;
repaint();
}
/**
* Returns the size (number of elements) of the memory.
*/
public int getMemorySize() {
return values != null ? values.length : 0;
}
/**
* Returns the value at the given index in its string representation.
*/
public String getValueAsString(int index) {
return Format.translateValueToString(values[index], dataFormat);
}
/**
* Selects the commands in the range fromIndex..toIndex
*/
public void select(int fromIndex,int toIndex) {
memoryTable.setRowSelectionInterval(fromIndex,toIndex);
Utilities.tableCenterScroll(this, memoryTable, fromIndex);
}
/**
* Hides all selections.
*/
public void hideSelect() {
memoryTable.clearSelection();
repaint();
}
/**
* Returns the coordinates of the top left corner of the value at the given index.
*/
public Point getCoordinates (int index) {
JScrollBar bar = scrollPane.getVerticalScrollBar();
Rectangle r = memoryTable.getCellRect(index, getValueColumnIndex(), true);
memoryTable.scrollRectToVisible(r);
return new Point((int)(r.getX() + topLevelLocation.getX()),
(int)(r.getY() + topLevelLocation.getY() - bar.getValue()));
}
/**
* Returns the address string at a specific address.
*/
public String getAddressStr (short address) {
return addresses[address];
}
/**
* Returns the value (in a string representation) at a specific address.
*/
public String getValueStr (short address) {
return valuesStr[address];
}
/**
* Returns the value (in a short representation) at a specific address.
*/
public short getValueAsShort (short address) {
return values[address];
}
/**
* Translates a given string to a short according to the current format.
* Throws a TranslationException if can't be translated.
*/
protected short translateValueToShort(String data) throws TranslationException {
short result = 0;
try {
result = Format.translateValueToShort(data,dataFormat);
} catch (NumberFormatException nfe) {
throw new TranslationException("Illegal value: " + data);
}
return result;
}
/**
* Translates a given short to a string according to the current format.
*/
protected String translateValueToString(short value) {
if(hideNullValue) {
if(value == nullValue)
return "";
else
return Format.translateValueToString(value, dataFormat);
}
else
return Format.translateValueToString(value, dataFormat);
}
/**
* Sets the font of the table.
*/
public void setTableFont (Font font) {
memoryTable.setFont(font);
}
// Initializes this memory.
private void jbInit(){
memoryTable.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
memoryTable_focusGained(e);
}
public void focusLost(FocusEvent e) {
memoryTable_focusLost(e);
}
});
scrollPane = new JScrollPane(memoryTable);
this.setLayout(null);
searchButton.setToolTipText("Search");
searchButton.setIcon(searchIcon);
searchButton.setBounds(new Rectangle(159, 2, 31, 25));
searchButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
searchButton_actionPerformed(e);
}
});
memoryTable.setFont(Utilities.valueFont);
nameLbl.setBounds(new Rectangle(3, 5, 70, 23));
nameLbl.setFont(Utilities.labelsFont);
determineColumnWidth();
setBorder(BorderFactory.createEtchedBorder());
scrollPane.setLocation(0,27);
clearButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
clearButton_actionPerformed(e);
}
});
clearButton.setIcon(clearIcon);
clearButton.setBounds(new Rectangle(128, 2, 31, 25));
clearButton.setToolTipText("Clear");
this.add(scrollPane, null);
this.add(searchButton, null);
this.add(nameLbl, null);
this.add(clearButton, null);
}
/**
* Returns the width of the table.
*/
public int getTableWidth() {
return 193;
}
/**
* Sets the number of visible rows.
*/
public void setVisibleRows(int num) {
int tableHeight = num * memoryTable.getRowHeight();
scrollPane.setSize(getTableWidth(), tableHeight + 3);
setPreferredSize(new Dimension(getTableWidth(), tableHeight + 30));
setSize(getTableWidth(), tableHeight + 30);
}
// Determines the width of each column in the table.
protected void determineColumnWidth() {
TableColumn column = null;
for (int i = 0; i < 2; i++) {
column = memoryTable.getColumnModel().getColumn(i);
if (i == 0) {
column.setPreferredWidth(30);
} else {
column.setPreferredWidth(100);
}
}
}
public void scrollTo(int index) {
Utilities.tableCenterScroll(this, memoryTable, index);
}
/**
* Implementing the action of the table gaining the focus.
*/
public void memoryTable_focusGained(FocusEvent e) {
memoryTable.clearSelection();
notifyListeners();
}
/**
* Implementing the action of the table loosing the focus.
*/
public void memoryTable_focusLost(FocusEvent e) {
memoryTable.clearSelection();
}
/**
* Implementing the action of pressing the search button.
*/
public void searchButton_actionPerformed(ActionEvent e) {
searchWindow.showWindow();
}
/**
* Implementing the action of pressing the clear button.
*/
public void clearButton_actionPerformed(ActionEvent e) {
Object[] options = {"Yes", "No","Cancel"};
int pressedButtonValue = JOptionPane.showOptionDialog(this.getParent(),
"Are you sure you want to clear ?",
"Warning Message",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE,
null,
options,
options[2]);
if(pressedButtonValue==JOptionPane.YES_OPTION)
notifyClearListeners();
}
// An inner class representing the model of this table.
class MemoryTableModel extends AbstractTableModel {
/**
* Returns the number of columns.
*/
public int getColumnCount() {
return 2;
}
/**
* Returns the number of rows.
*/
public int getRowCount() {
return getMemorySize();
}
/**
* Returns the names of the columns.
*/
public String getColumnName(int col) {
return null;
}
/**
* Returns the value at a specific row and column.
*/
public Object getValueAt(int row, int col) {
if(col==0)
return addresses[row];
else
return valuesStr[row];
}
/**
* Returns true of this table cells are editable, false -
* otherwise.
*/
public boolean isCellEditable(int row, int col) {
boolean result = false;
if(isEnabled && col == 1 &&
(endEnabling == -1 || (row>= startEnabling && row <= endEnabling)))
result = true;
return result;
}
/**
* Sets the value at a specific row and column.
*/
public void setValueAt(Object value, int row, int col) {
String data = ((String)value).trim();
if (!valuesStr[row].equals(data)) {
try {
valuesStr[row] = data;
if(data.equals("") && hideNullValue)
values[row] = nullValue;
else
values[row] = translateValueToShort(data);
notifyListeners((short)row,values[row]);
} catch(TranslationException te) {
notifyErrorListeners(te.getMessage());
valuesStr[row] = translateValueToString(values[row]);
}
repaint();
notifyRepaintListeners();
}
}
}
/**
* Sets the numeric format with the given code (out of the format constants
* in HackController).
*/
public void setNumericFormat(int formatCode) {
dataFormat = formatCode;
for(int i=0;i<values.length; i++)
valuesStr[i] = translateValueToString(values[i]);
repaint();
notifyRepaintListeners();
}
/**
* Sets the size of the name label according to the size constants.
*/
public void setNameLabelSize() {
nameLbl.setBounds(new Rectangle(3, 7, 150, 23));
}
// An inner class which implemets the cell renderer of the memory table, giving
// the feature of aligning the text in the cells.
class MemoryTableCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent
(JTable table, Object value, boolean selected, boolean focused, int row, int column)
{
setForeground(null);
setBackground(null);
setRenderer(row, column);
super.getTableCellRendererComponent(table, value, selected, focused, row, column);
return this;
}
public void setRenderer(int row, int column) {
if(column == 0)
setHorizontalAlignment(SwingConstants.CENTER);
else if (column == 1) {
setHorizontalAlignment(SwingConstants.RIGHT);
for (int i=0;i<highlightIndex.size(); i++) {
if(row == ((Integer)highlightIndex.elementAt(i)).intValue()) {
setForeground(Color.blue);
break;
}
}
if (row == flashIndex)
setBackground(Color.orange);
}
if (row < startEnabling || row > endEnabling && grayDisabledRange)
setForeground(Color.lightGray);
}
}
}