content: package mars.venus;
  
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.Container;
  import java.awt.GridLayout;
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.Toolkit;
  import java.awt.event.ActionEvent;
  import java.awt.event.InputEvent;
  import java.awt.event.ItemEvent;
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
  import java.util.Date;
  import java.util.Observable;
  import java.util.Observer;
  
  import javax.swing.DefaultComboBoxModel;
  import javax.swing.Icon;
  import javax.swing.ImageIcon;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
  import javax.swing.JInternalFrame;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JTable;
  import javax.swing.ScrollPaneConstants;
  import javax.swing.SwingConstants;
  import javax.swing.event.TableModelEvent;
  import javax.swing.table.AbstractTableModel;
  import javax.swing.table.DefaultTableCellRenderer;
  import javax.swing.table.JTableHeader;
  import javax.swing.table.TableModel;
  
  import mars.Globals;
  import mars.Settings;
  import mars.mips.hardware.AccessNotice;
  import mars.mips.hardware.AddressErrorException;
  import mars.mips.hardware.Memory;
  import mars.mips.hardware.MemoryAccessNotice;
  import mars.mips.hardware.RegisterFile;
  import mars.simulator.Simulator;
  import mars.simulator.SimulatorNotice;
  import mars.util.Binary;
  
  /*
   * Copyright (c) 2003-2013, 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)
   */
  
  /**
   * Represents the Data Segment window, which is a type of JInternalFrame.
   *
   * @author Sanderson and Bumgarner
   **/
  
  public class DataSegmentWindow extends JInternalFrame implements Observer {
  
  	/**
  	 *
  	 */
  	private static final long serialVersionUID = 1952058821314213190L;
  	private static final String[] dataSegmentNames = { "Data", "Stack", "Kernel" };
  	private static Object[][] dataData;
  
  	private static JTable dataTable;
  	private JScrollPane dataTableScroller;
  	private final Container contentPane;
  	private final JPanel tablePanel;
  	private JButton dataButton, nextButton, prevButton, stakButton, globButton, heapButton, kernButton, extnButton,
  			mmioButton, textButton;
  	private final JCheckBox asciiDisplayCheckBox;
  
  	static final int VALUES_PER_ROW = 8;
  	static final int NUMBER_OF_ROWS = 16;  // with 8 value columns, this shows 512 bytes;  
  	static final int NUMBER_OF_COLUMNS = VALUES_PER_ROW + 1;// 1 for address and 8 for values
  	static final int BYTES_PER_VALUE = 4;
  	static final int BYTES_PER_ROW = VALUES_PER_ROW * BYTES_PER_VALUE;
  	static final int MEMORY_CHUNK_SIZE = NUMBER_OF_ROWS * BYTES_PER_ROW;
  	// PREV_NEXT_CHUNK_SIZE determines how many rows will be scrolled when Prev or Next buttons fire.
  	// MEMORY_CHUNK_SIZE/2 means scroll half a table up or down.  Easier to view series that flows off the edge.
  	// MEMORY_CHUNK_SIZE means scroll a full table's worth.  Scrolls through memory faster.  DPS 26-Jan-09
  	static final int PREV_NEXT_CHUNK_SIZE = MEMORY_CHUNK_SIZE / 2;
  	static final int ADDRESS_COLUMN = 0;
  	static final boolean USER_MODE = false;
  	static final boolean KERNEL_MODE = true;
  
  	private boolean addressHighlighting = false;
  	private boolean asciiDisplay = false;
  	private int addressRow, addressColumn, addressRowFirstAddress;
  	private final Settings settings;
  
  	int firstAddress;
  	int homeAddress;
  	boolean userOrKernelMode;
  
  	// The combo box replaced the row of buttons when number of buttons expanded to 7!
  	// We'll keep the button objects however and manually invoke their action listeners
  	// when the corresponding combo box item is selected.  DPS 22-Nov-2006
  	JComboBox baseAddressSelector;
  
  	// The next bunch are initialized dynamically in initializeBaseAddressChoices()
  	private String[] displayBaseAddressChoices;
  	private int[] displayBaseAddresses;
  	private int defaultBaseAddressIndex;
  	JButton[] baseAddressButtons;
  
  	/**
  	 * Constructor for the Data Segment window.
  	 *
  	 * @param choosers an array of objects used by user to select number display
  	 *                 base (10 or 16)
  	 */
  
  	public DataSegmentWindow(final NumberDisplayBaseChooser[] choosers) {
  		super("Data Segment", true, false, true, true);
  
  		Simulator.getInstance().addObserver(this);
  		settings = Globals.getSettings();
  		settings.addObserver(this);
  
  		homeAddress = Memory.dataBaseAddress;  // address for Home button
  		firstAddress = homeAddress;  // first address to display at any given time
  		userOrKernelMode = USER_MODE;
  		addressHighlighting = false;
  		contentPane = getContentPane();
  		tablePanel = new JPanel(new GridLayout(1, 2, 10, 0));
  		final JPanel features = new JPanel();
  		final Toolkit tk = Toolkit.getDefaultToolkit();
  		final Class cs = this.getClass();
  		try {
  			prevButton = new PrevButton(new ImageIcon(tk.getImage(cs.getResource(Globals.imagesPath
  					+ "Previous22.png"))));//"Back16.gif"))));//"Down16.gif"))));
  			nextButton = new NextButton(new ImageIcon(tk.getImage(cs.getResource(Globals.imagesPath + "Next22.png"))));//"Forward16.gif")))); //"Up16.gif"))));
  			//  This group of buttons was replaced by a combo box.  Keep the JButton objects for their action listeners.
  			dataButton = new JButton();//".data");
  			stakButton = new JButton();//"$sp");
  			globButton = new JButton();//"$gp");
  			heapButton = new JButton();//"heap");
  			extnButton = new JButton();//".extern");
  			mmioButton = new JButton();//"MMIO");
  			textButton = new JButton();//".text");
  			kernButton = new JButton();//".kdata");
  		} catch (final NullPointerException e) {
  			System.out.println("Internal Error: images folder not found");
  			System.exit(0);
  		}
  
  		initializeBaseAddressChoices();
  		baseAddressSelector = new JComboBox();
  		baseAddressSelector.setModel(new CustomComboBoxModel(displayBaseAddressChoices));
  		baseAddressSelector.setEditable(false);
  		baseAddressSelector.setSelectedIndex(defaultBaseAddressIndex);
  		baseAddressSelector.setToolTipText("Base address for data segment display");
  		baseAddressSelector.addActionListener(e -> baseAddressButtons[baseAddressSelector.getSelectedIndex()]
  				.getActionListeners()[0].actionPerformed(null));
  
  		addButtonActionListenersAndInitialize();
  		final JPanel navButtons = new JPanel(new GridLayout(1, 4));
  		navButtons.add(prevButton);
  		navButtons.add(nextButton);
  		features.add(navButtons);
  		features.add(baseAddressSelector);
  		for (int i = 0; i < choosers.length; i++) {
  			features.add(choosers[i]);
  		}
  		asciiDisplayCheckBox = new JCheckBox("ASCII", asciiDisplay);
  		asciiDisplayCheckBox.setToolTipText(
  				"Display data segment values in ASCII (overrides Hexadecimal Values setting)");
  		asciiDisplayCheckBox.addItemListener(e -> {
  			asciiDisplay = e.getStateChange() == ItemEvent.SELECTED;
  			DataSegmentWindow.this.updateValues();
  		});
  		features.add(asciiDisplayCheckBox);
  
  		contentPane.add(features, BorderLayout.SOUTH);
  	}
  
  	public void updateBaseAddressComboBox() {
  		displayBaseAddressArray[EXTERN_BASE_ADDRESS_INDEX] = Memory.externBaseAddress;
  		displayBaseAddressArray[GLOBAL_POINTER_ADDRESS_INDEX] = -1; /*Memory.globalPointer*/
  		displayBaseAddressArray[DATA_BASE_ADDRESS_INDEX] = Memory.dataBaseAddress;
  		displayBaseAddressArray[HEAP_BASE_ADDRESS_INDEX] = Memory.heapBaseAddress;
  		displayBaseAddressArray[STACK_POINTER_BASE_ADDRESS_INDEX] = -1; /*Memory.stackPointer*/
  		displayBaseAddressArray[KERNEL_DATA_BASE_ADDRESS_INDEX] = Memory.kernelDataBaseAddress;
  		displayBaseAddressArray[MMIO_BASE_ADDRESS_INDEX] = Memory.memoryMapBaseAddress;
  		displayBaseAddressArray[TEXT_BASE_ADDRESS_INDEX] = Memory.textBaseAddress;
  		displayBaseAddressChoices = createBaseAddressLabelsArray(displayBaseAddressArray, descriptions);
  		baseAddressSelector.setModel(new CustomComboBoxModel(displayBaseAddressChoices));
  		displayBaseAddresses = displayBaseAddressArray;
  		baseAddressSelector.setSelectedIndex(defaultBaseAddressIndex);
  	}
  
  	/**
  	 * Scroll the viewport so the cell at the given data 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. Note there is a separate method to highlight
  	 * the cell by setting its background color to a highlighting color. Thus one
  	 * cell can be highlighted while a different cell is selected at the same time.
  	 *
  	 * @param address data segment address of word to be selected.
  	 */
  	void selectCellForAddress(final int address) {
  		final Point rowColumn = displayCellForAddress(address);
  		if (rowColumn == null) { return; }
  		final Rectangle addressCell = dataTable.getCellRect(rowColumn.x, rowColumn.y, true);
  		// Select the memory address cell by generating a fake Mouse Pressed event within its
  		// extent and explicitly invoking the table's mouse listener.
  		final MouseEvent fakeMouseEvent = new MouseEvent(dataTable, MouseEvent.MOUSE_PRESSED, new Date().getTime(),
  				InputEvent.BUTTON1_MASK, (int) addressCell.getX() + 1, (int) addressCell.getY() + 1, 1, false);
  		final MouseListener[] mouseListeners = dataTable.getMouseListeners();
  		for (int i = 0; i < mouseListeners.length; i++) {
  			mouseListeners[i].mousePressed(fakeMouseEvent);
  		}
  	}
  
  	/**
  	 * Scroll the viewport so the cell at the given data segment address is visible,
  	 * vertically centered if possible, and highlighted (but not selected).
  	 *
  	 * @param address data segment address of word to be selected.
  	 */
  	void highlightCellForAddress(final int address) {
  		final Point rowColumn = displayCellForAddress(address);
  		if (rowColumn == null || rowColumn.x < 0 || rowColumn.y < 0) { return; }
  		addressRow = rowColumn.x;
  		addressColumn = rowColumn.y;
  		addressRowFirstAddress = Binary.stringToInt(dataTable.getValueAt(addressRow, ADDRESS_COLUMN).toString());
  		//System.out.println("Address "+Binary.intToHexString(address)+" becomes row "+ addressRow + " column "+addressColumn+
  		//" starting addr "+dataTable.getValueAt(this.addressRow,ADDRESS_COLUMN));
  		// Tell the system that table contents have changed.  This will trigger re-rendering
  		// during which cell renderers are obtained.  The cell of interest (identified by
  		// instance variables this.addressRow and this.addressColumn) will get a renderer
  		// with highlight background color and all others get renderer with default background.
  		dataTable.tableChanged(new TableModelEvent(dataTable.getModel(), 0, dataData.length - 1));
  	}
  
  	// Given address, will compute table cell location, adjusting table if necessary to
  	// contain this cell, make sure that cell is visible, then return a Point containing
  	// row and column position of cell in the table.  This private helper method is called
  	// by selectCellForAddress() and highlightCellForAddress().
  	// This is the kind of design I tell my students to avoid! The method both translates
  	// address to table cell coordinates and adjusts the display to assure the cell is visible.
  	// The two operations are related because the address may fall in within address space not
  	// currently in the (display) table, including a different MIPS data segment (e.g. in
  	// kernel instead of user data segment).
  	private Point displayCellForAddress(final int address) {
  		//////////////////////////////////////////////////////////
  		// This requires a 5-step process.  Each step is described
  		// just above the statements that implement it.
  		//////////////////////////////////////////////////////////
  
  		// STEP 1: Determine which data segment contains this address.
  		final int desiredComboBoxIndex = getBaseAddressIndexForAddress(address);
  		if (desiredComboBoxIndex < 0) {
  			// It is not a data segment address so good bye!
  			return null;
  		}
  		// STEP 2:  Set the combo box appropriately.  This will also display the
  		// first chunk of addresses from that segment.
  		baseAddressSelector.setSelectedIndex(desiredComboBoxIndex);
  		((CustomComboBoxModel) baseAddressSelector.getModel()).forceComboBoxUpdate(desiredComboBoxIndex);
  		baseAddressButtons[desiredComboBoxIndex].getActionListeners()[0].actionPerformed(null);
  		// STEP 3:  Display memory chunk containing this address, which may be
  		// different than the one just displayed.
  		int baseAddress = displayBaseAddressArray[desiredComboBoxIndex];
  		if (baseAddress == -1) {
  			if (desiredComboBoxIndex == GLOBAL_POINTER_ADDRESS_INDEX) {
  				baseAddress = RegisterFile.getValue(RegisterFile.GLOBAL_POINTER_REGISTER) - RegisterFile.getValue(
  						RegisterFile.GLOBAL_POINTER_REGISTER) % BYTES_PER_ROW;
  			} else if (desiredComboBoxIndex == STACK_POINTER_BASE_ADDRESS_INDEX) {
  				baseAddress = RegisterFile.getValue(RegisterFile.STACK_POINTER_REGISTER) - RegisterFile.getValue(
  						RegisterFile.STACK_POINTER_REGISTER) % BYTES_PER_ROW;
  			} else {
  				return null;// shouldn't happen since these are the only two
  			}
  		}
  		final int byteOffset = address - baseAddress;
  		final int chunkOffset = byteOffset / MEMORY_CHUNK_SIZE;
  		final int byteOffsetIntoChunk = byteOffset % MEMORY_CHUNK_SIZE;
  		// Subtract 1 from chunkOffset because we're gonna call the "next" action
  		// listener to get the correct chunk loaded and displayed, and the first
  		// thing it does is increment firstAddress by MEMORY_CHUNK_SIZE.  Here
  		// we do an offsetting decrement in advance because we don't want the
  		// increment but we want the other actions that method provides.
  		firstAddress = firstAddress + chunkOffset * MEMORY_CHUNK_SIZE - PREV_NEXT_CHUNK_SIZE;
  		nextButton.getActionListeners()[0].actionPerformed(null);
  		// STEP 4:  Find cell containing this address.  Add 1 to column calculation
  		// because table column 0 displays address, not memory contents.  The
  		// "convertColumnIndexToView()" is not necessary because the columns cannot be
  		// reordered, but I included it as a precautionary measure in case that changes.
  		final int addrRow = byteOffsetIntoChunk / BYTES_PER_ROW;
  		int addrColumn = byteOffsetIntoChunk % BYTES_PER_ROW / BYTES_PER_VALUE + 1;
  		addrColumn = dataTable.convertColumnIndexToView(addrColumn);
  		final Rectangle addressCell = dataTable.getCellRect(addrRow, addrColumn, true);
  		// STEP 5:  Center the row containing the cell of interest, to the extent possible.
  		final double cellHeight = addressCell.getHeight();
  		final double viewHeight = dataTableScroller.getViewport().getExtentSize().getHeight();
  		final int numberOfVisibleRows = (int) (viewHeight / cellHeight);
  		final int newViewPositionY = Math.max((int) ((addrRow - numberOfVisibleRows / 2) * cellHeight), 0);
  		dataTableScroller.getViewport().setViewPosition(new Point(0, newViewPositionY));
  		return new Point(addrRow, addrColumn);
  	}
  
  	////////////////////////////////////////////////////////////////////////
  	// Initalize arrays used with Base Address combo box chooser.
  	// The combo box replaced the row of buttons when number of buttons expanded to 7!
  	private static final int EXTERN_BASE_ADDRESS_INDEX = 0;
  	private static final int GLOBAL_POINTER_ADDRESS_INDEX = 3; //1;
  	private static final int TEXT_BASE_ADDRESS_INDEX = 5; //2;
  	private static final int DATA_BASE_ADDRESS_INDEX = 1; //3;
  	private static final int HEAP_BASE_ADDRESS_INDEX = 2; //4;
  	private static final int STACK_POINTER_BASE_ADDRESS_INDEX = 4; //5;
  	private static final int KERNEL_DATA_BASE_ADDRESS_INDEX = 6;
  	private static final int MMIO_BASE_ADDRESS_INDEX = 7;
  	// Must agree with above in number and order...
  	private final int[] displayBaseAddressArray = { Memory.externBaseAddress, Memory.dataBaseAddress,
  			Memory.heapBaseAddress, -1 /*Memory.globalPointer*/, -1 /*Memory.stackPointer*/, Memory.textBaseAddress,
  			Memory.kernelDataBaseAddress, Memory.memoryMapBaseAddress, };
  	// Must agree with above in number and order...
  	String[] descriptions = { " (.extern)", " (.data)", " (heap)", "current $gp", "current $sp", " (.text)",
  			" (.kdata)", " (MMIO)" };
  
  	private void initializeBaseAddressChoices() {
  		// Also must agree in number and order.  Upon combo box item selection, will invoke
  		// action listener for that item's button.
  		baseAddressButtons = new JButton[descriptions.length];
  		baseAddressButtons[EXTERN_BASE_ADDRESS_INDEX] = extnButton;
  		baseAddressButtons[GLOBAL_POINTER_ADDRESS_INDEX] = globButton;
  		baseAddressButtons[DATA_BASE_ADDRESS_INDEX] = dataButton;
  		baseAddressButtons[HEAP_BASE_ADDRESS_INDEX] = heapButton;
  		baseAddressButtons[STACK_POINTER_BASE_ADDRESS_INDEX] = stakButton;
  		baseAddressButtons[KERNEL_DATA_BASE_ADDRESS_INDEX] = kernButton;
  		baseAddressButtons[MMIO_BASE_ADDRESS_INDEX] = mmioButton;
  		baseAddressButtons[TEXT_BASE_ADDRESS_INDEX] = textButton;
  		displayBaseAddresses = displayBaseAddressArray;
  		displayBaseAddressChoices = createBaseAddressLabelsArray(displayBaseAddressArray, descriptions);
  		defaultBaseAddressIndex = DATA_BASE_ADDRESS_INDEX;
  	}
  
  	// Create and fill String array containing labels for base address combo box.
  	private String[] createBaseAddressLabelsArray(final int[] baseAddressArray, final String[] descriptions) {
  		final String[] baseAddressChoices = new String[baseAddressArray.length];
  		for (int i = 0; i < baseAddressChoices.length; i++) {
  			baseAddressChoices[i] = (baseAddressArray[i] != -1 ? mars.util.Binary.intToHexString(baseAddressArray[i])
  					: "") + descriptions[i];
  		}
  		return baseAddressChoices;
  	}
  
  	// Given an address, determine which segment it is in and return the corresponding
  	// combo box index.  Note there is not a one-to-one correspondence between these
  	// indexes and the Memory tables.  For instance, the heap (0x10040000), the
  	// global (0x10008000) and the data segment base (0x10000000) are all stored in the
  	// same table as the static (0x10010000) so all are "Memory.inDataSegment()".
  	private int getBaseAddressIndexForAddress(final int address) {
  		int desiredComboBoxIndex = -1; // assume not a data address.
  		if (Memory.inKernelDataSegment(address)) {
  			return KERNEL_DATA_BASE_ADDRESS_INDEX;
  		} else if (Memory.inMemoryMapSegment(address)) {
  			return MMIO_BASE_ADDRESS_INDEX;
  		} else if (Memory.inTextSegment(address)) { // DPS. 8-July-2013
  			return TEXT_BASE_ADDRESS_INDEX;
  		}
  		int shortDistance = 0x7fffffff;
  		int thisDistance;
  		// Check distance from .extern base.  Cannot be below it
  		thisDistance = address - Memory.externBaseAddress;
  		if (thisDistance >= 0 && thisDistance < shortDistance) {
  			shortDistance = thisDistance;
  			desiredComboBoxIndex = EXTERN_BASE_ADDRESS_INDEX;
  		}
  		// Check distance from global pointer; can be either side of it...
  		thisDistance = Math.abs(address - RegisterFile.getValue(RegisterFile.GLOBAL_POINTER_REGISTER)); // distance from global pointer
  		if (thisDistance < shortDistance) {
  			shortDistance = thisDistance;
  			desiredComboBoxIndex = GLOBAL_POINTER_ADDRESS_INDEX;
  		}
  		// Check distance from .data base.  Cannot be below it
  		thisDistance = address - Memory.dataBaseAddress;
  		if (thisDistance >= 0 && thisDistance < shortDistance) {
  			shortDistance = thisDistance;
  			desiredComboBoxIndex = DATA_BASE_ADDRESS_INDEX;
  		}
  		// Check distance from heap base.  Cannot be below it
  		thisDistance = address - Memory.heapBaseAddress;
  		if (thisDistance >= 0 && thisDistance < shortDistance) {
  			shortDistance = thisDistance;
  			desiredComboBoxIndex = HEAP_BASE_ADDRESS_INDEX;
  		}
  		// Check distance from stack pointer.  Can be on either side of it...
  		thisDistance = Math.abs(address - RegisterFile.getValue(RegisterFile.STACK_POINTER_REGISTER));
  		if (thisDistance < shortDistance) {
  			shortDistance = thisDistance;
  			desiredComboBoxIndex = STACK_POINTER_BASE_ADDRESS_INDEX;
  		}
  		return desiredComboBoxIndex;
  	}
  
  	////////////////////////////////////////////////////////////////////////////////
  	//  Generates the Address/Data part of the Data Segment window.
  	//   Returns the JScrollPane for the Address/Data part of the Data Segment window.
  	private JScrollPane generateDataPanel() {
  		dataData = new Object[NUMBER_OF_ROWS][NUMBER_OF_COLUMNS];
  		final int valueBase = Globals.getGui().getMainPane().getExecutePane().getValueDisplayBase();
  		final int addressBase = Globals.getGui().getMainPane().getExecutePane().getAddressDisplayBase();
  		int address = homeAddress;
  		for (int row = 0; row < NUMBER_OF_ROWS; row++) {
  			dataData[row][ADDRESS_COLUMN] = NumberDisplayBaseChooser.formatUnsignedInteger(address, addressBase);
  			for (int column = 1; column < NUMBER_OF_COLUMNS; column++) {
  				try {
  					dataData[row][column] = NumberDisplayBaseChooser.formatNumber(Globals.memory.getRawWord(address),
  							valueBase);
  				} catch (final AddressErrorException aee) {
  					dataData[row][column] = NumberDisplayBaseChooser.formatNumber(0, valueBase);
  				}
  				address += BYTES_PER_VALUE;
  			}
  		}
  		final String[] names = new String[NUMBER_OF_COLUMNS];
  		for (int i = 0; i < NUMBER_OF_COLUMNS; i++) {
  			names[i] = getHeaderStringForColumn(i, addressBase);
  		}
  		dataTable = new MyTippedJTable(new DataTableModel(dataData, names));
  		// Do not allow user to re-order columns; column order corresponds to MIPS memory order
  		dataTable.getTableHeader().setReorderingAllowed(false);
  		dataTable.setRowSelectionAllowed(false);
  		// Addresses are column 0, render right-justified in mono font
  		final MonoRightCellRenderer monoRightCellRenderer = new MonoRightCellRenderer();
  		dataTable.getColumnModel().getColumn(ADDRESS_COLUMN).setPreferredWidth(60);
  		dataTable.getColumnModel().getColumn(ADDRESS_COLUMN).setCellRenderer(monoRightCellRenderer);
  		// Data cells are columns 1 onward, render right-justitifed in mono font but highlightable.
  		final AddressCellRenderer addressCellRenderer = new AddressCellRenderer();
  		for (int i = 1; i < NUMBER_OF_COLUMNS; i++) {
  			dataTable.getColumnModel().getColumn(i).setPreferredWidth(60);
  			dataTable.getColumnModel().getColumn(i).setCellRenderer(addressCellRenderer);
  		}
  		dataTableScroller = new JScrollPane(dataTable, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
  				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
  		return dataTableScroller;
  	}
  
  	// Little helper.  Is called when headers set up and each time number base changes.
  	private String getHeaderStringForColumn(final int i, final int base) {
  		return i == ADDRESS_COLUMN ? "Address" : "Value (+" + Integer.toString((i - 1) * BYTES_PER_VALUE, base) + ")";
  	}
  
  	/**
  	 * Generates and displays fresh table, typically done upon successful assembly.
  	 */
  	public void setupTable() {
  		tablePanel.removeAll();
  		tablePanel.add(generateDataPanel());
  		contentPane.add(tablePanel);
  		enableAllButtons();
  	}
  
  	/**
  	 * Removes the table from its frame, typically done when a file is closed.
  	 */
  	public void clearWindow() {
  		tablePanel.removeAll();
  		disableAllButtons();
  	}
  
  	/**
  	 * Clear highlight background color from any cell currently highlighted.
  	 */
  	public void clearHighlighting() {
  		addressHighlighting = false;
  		dataTable.tableChanged(new TableModelEvent(dataTable.getModel(), 0, dataData.length - 1));
  		// The below addresses situation in which addressRow and addressColum hold their
  		// values across assemble operations.  Whereupon at the first step of the next
  		// run the last cells from the previous run are highlighted!  This method is called
  		// after each successful assemble (or reset, which just re-assembles).  The
  		// assignment below assures the highlighting condition column==addressColumn will be
  		// initially false since column>=0.  DPS 23 jan 2009
  		addressColumn = -1;
  	}
  
  	private int getValueDisplayFormat() {
  		return asciiDisplay ? NumberDisplayBaseChooser.ASCII
  				: Globals.getGui().getMainPane().getExecutePane().getValueDisplayBase();
  	}
  
  	/**
  	 * Update table model with contents of new memory "chunk". Mars supports
  	 * megabytes of data segment space so we only plug a "chunk" at a time into the
  	 * table.
  	 *
  	 * @param firstAddr the first address in the memory range to be placed in the
  	 *                  model.
  	 */
  
  	public void updateModelForMemoryRange(final int firstAddr) {
  		if (tablePanel.getComponentCount() == 0) {
  			return; // ignore if no content to change
  		}
  		final int valueBase = getValueDisplayFormat();
  		final int addressBase = Globals.getGui().getMainPane().getExecutePane().getAddressDisplayBase();
  		int address = firstAddr;
  		final TableModel dataModel = dataTable.getModel();
  		for (int row = 0; row < NUMBER_OF_ROWS; row++) {
  			((DataTableModel) dataModel).setDisplayAndModelValueAt(NumberDisplayBaseChooser.formatUnsignedInteger(
  					address, addressBase), row, ADDRESS_COLUMN);
  			for (int column = 1; column < NUMBER_OF_COLUMNS; column++) {
  				try {
  					((DataTableModel) dataModel).setDisplayAndModelValueAt(NumberDisplayBaseChooser.formatNumber(
  							Globals.memory.getWordNoNotify(address), valueBase), row, column);
  				} catch (final AddressErrorException aee) {
  					// Bit of a hack here.  Memory will throw an exception if you try to read directly from text segment when the
  					// self-modifying code setting is disabled.  This is a good thing if it is the executing MIPS program trying to
  					// read.  But not a good thing if it is the DataSegmentDisplay trying to read.  I'll trick Memory by
  					// temporarily enabling the setting as "non persistent" so it won't write through to the registry.
  					if (Memory.inTextSegment(address)) {
  						int displayValue = 0;
  						if (!Globals.getSettings().getBooleanSetting(Settings.SELF_MODIFYING_CODE_ENABLED)) {
  							Globals.getSettings().setBooleanSettingNonPersistent(Settings.SELF_MODIFYING_CODE_ENABLED,
  									true);
  							try {
  								displayValue = Globals.memory.getWordNoNotify(address);
  							} catch (final AddressErrorException e) {
  								// Still got an exception?  Doesn't seem possible but if we drop through it will write default value 0.
  							}
  							Globals.getSettings().setBooleanSettingNonPersistent(Settings.SELF_MODIFYING_CODE_ENABLED,
  									false);
  						}
  						((DataTableModel) dataModel).setDisplayAndModelValueAt(NumberDisplayBaseChooser.formatNumber(
  								displayValue, valueBase), row, column);
  					}
  					// Bug Fix: the following line of code disappeared during the release 4.4 mods, but is essential to
  					// display values of 0 for valid MIPS addresses that are outside the MARS simulated address space.  Such
  					// addresses cause an AddressErrorException.  Prior to 4.4, they performed this line of code unconditionally.
  					// With 4.4, I added the above IF statement to work with the text segment but inadvertently removed this line!
  					// Now it becomes the "else" part, executed when not in text segment.  DPS 8-July-2014.
  					else {
  						((DataTableModel) dataModel).setDisplayAndModelValueAt(NumberDisplayBaseChooser.formatNumber(0,
  								valueBase), row, column);
  					}
  				}
  				address += BYTES_PER_VALUE;
  			}
  		}
  	}
  
  	/**
  	 * Update data display to show this value (I'm not sure it is being called).
  	 */
  
  	public void updateCell(final int address, final int value) {
  		final int offset = address - firstAddress;
  		if (offset < 0 || offset >= MEMORY_CHUNK_SIZE) { // out of range
  			return;
  		}
  		final int row = offset / BYTES_PER_ROW;
  		final int column = offset % BYTES_PER_ROW / BYTES_PER_VALUE + 1; // column 0 reserved for address
  		final int valueBase = Globals.getGui().getMainPane().getExecutePane().getValueDisplayBase();
  		((DataTableModel) dataTable.getModel()).setDisplayAndModelValueAt(NumberDisplayBaseChooser.formatNumber(value,
  				valueBase), row, column);
  	}
  
  	/**
  	 * 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 updateDataAddresses() {
  		if (tablePanel.getComponentCount() == 0) {
  			return; // ignore if no content to change
  		}
  		final int addressBase = Globals.getGui().getMainPane().getExecutePane().getAddressDisplayBase();
  		int address = firstAddress;
  		String formattedAddress;
  		for (int i = 0; i < NUMBER_OF_ROWS; i++) {
  			formattedAddress = NumberDisplayBaseChooser.formatUnsignedInteger(address, addressBase);
  			((DataTableModel) dataTable.getModel()).setDisplayAndModelValueAt(formattedAddress, i, 0);
  			address += BYTES_PER_ROW;
  		}
  		// column headers include address offsets, so translate them too
  		for (int i = 1; i < NUMBER_OF_COLUMNS; i++) {
  			dataTable.getColumnModel().getColumn(i).setHeaderValue(getHeaderStringForColumn(i, addressBase));
  		}
  		dataTable.getTableHeader().repaint();
  	}
  
  	/**
  	 * Update data display to show all values
  	 */
  
  	public void updateValues() {
  		updateModelForMemoryRange(firstAddress);
  	}
  
  	/**
  	 * Reset range of memory addresses to base address of currently selected segment
  	 * and update display.
  	 */
  
  	public void resetMemoryRange() {
  		baseAddressSelector.getActionListeners()[0].actionPerformed(null); // previously dataButton
  	}
  
  	/**
  	 * Reset all data display values to 0
  	 */
  
  	public void resetValues() {
  		final int valueBase = Globals.getGui().getMainPane().getExecutePane().getValueDisplayBase();
  		final TableModel dataModel = dataTable.getModel();
  		for (int row = 0; row < NUMBER_OF_ROWS; row++) {
  			for (int column = 1; column < NUMBER_OF_COLUMNS; column++) {
  				((DataTableModel) dataModel).setDisplayAndModelValueAt(NumberDisplayBaseChooser.formatNumber(0,
  						valueBase), row, column);
  			}
  		}
  		disableAllButtons();
  	}
  
  	/*
  	 * Do this initially and upon reset.
  	 */
  	private void disableAllButtons() {
  		baseAddressSelector.setEnabled(false);
  		globButton.setEnabled(false);
  		stakButton.setEnabled(false);
  		heapButton.setEnabled(false);
  		extnButton.setEnabled(false);
  		mmioButton.setEnabled(false);
  		textButton.setEnabled(false);
  		kernButton.setEnabled(false);
  		prevButton.setEnabled(false);
  		nextButton.setEnabled(false);
  		dataButton.setEnabled(false);
  	}
  
  	/*
  	 * Do this upon reset.
  	 */
  	private void enableAllButtons() {
  		baseAddressSelector.setEnabled(true);
  		globButton.setEnabled(true);
  		stakButton.setEnabled(true);
  		heapButton.setEnabled(true);
  		extnButton.setEnabled(true);
  		mmioButton.setEnabled(true);
  		textButton.setEnabled(settings.getBooleanSetting(Settings.SELF_MODIFYING_CODE_ENABLED));
  		kernButton.setEnabled(true);
  		prevButton.setEnabled(true);
  		nextButton.setEnabled(true);
  		dataButton.setEnabled(true);
  	}
  
  	/*
  	 * Establish action listeners for the data segment navigation buttons.
  	 */
  
  	private void addButtonActionListenersAndInitialize() {
  		// set initial states
  		disableAllButtons();
  		// add tool tips
  		// NOTE: For buttons that are now combo box items, the tool tips are not displayed w/o custom renderer.
  		globButton.setToolTipText("View range around global pointer");
  		stakButton.setToolTipText("View range around stack pointer");
  		heapButton.setToolTipText("View range around heap base address " + Binary.intToHexString(
  				Memory.heapBaseAddress));
  		kernButton.setToolTipText("View range around kernel data base address " + Binary.intToHexString(
  				Memory.kernelDataBaseAddress));
  		extnButton.setToolTipText("View range around static global base address " + Binary.intToHexString(
  				Memory.externBaseAddress));
  		mmioButton.setToolTipText("View range around MMIO base address " + Binary.intToHexString(
  				Memory.memoryMapBaseAddress));
  		textButton.setToolTipText("View range around program code " + Binary.intToHexString(Memory.textBaseAddress));
  		prevButton.setToolTipText("View next lower address range; hold down for rapid fire");
  		nextButton.setToolTipText("View next higher address range; hold down for rapid fire");
  		dataButton.setToolTipText("View range around static data segment base address " + Binary.intToHexString(
  				Memory.dataBaseAddress));
  
  		// add the action listeners to maintain button state and table contents
  		// Currently there is no memory upper bound so next button always enabled.
  
  		globButton.addActionListener(ae -> {
  			userOrKernelMode = USER_MODE;
  			// get $gp global pointer, but guard against it having value below data segment
  			firstAddress = Math.max(Memory.dataSegmentBaseAddress, RegisterFile.getValue(
  					RegisterFile.GLOBAL_POINTER_REGISTER));
  			// updateModelForMemoryRange requires argument to be multiple of 4
  			// but for cleaner display we'll make it multiple of 32 (last nibble is 0).
  			// This makes it easier to mentally calculate address from row address + column offset.
  			firstAddress = firstAddress - firstAddress % BYTES_PER_ROW;
  			homeAddress = firstAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		stakButton.addActionListener(ae -> {
  			userOrKernelMode = USER_MODE;
  			// get $sp stack pointer, but guard against it having value below data segment
  			firstAddress = Math.max(Memory.dataSegmentBaseAddress, RegisterFile.getValue(
  					RegisterFile.STACK_POINTER_REGISTER));
  			// See comment above for gloButton...
  			firstAddress = firstAddress - firstAddress % BYTES_PER_ROW;
  			homeAddress = Memory.stackBaseAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		heapButton.addActionListener(ae -> {
  			userOrKernelMode = USER_MODE;
  			homeAddress = Memory.heapBaseAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(homeAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		extnButton.addActionListener(ae -> {
  			userOrKernelMode = USER_MODE;
  			homeAddress = Memory.externBaseAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(homeAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		kernButton.addActionListener(ae -> {
  			userOrKernelMode = KERNEL_MODE;
  			homeAddress = Memory.kernelDataBaseAddress;
  			firstAddress = homeAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		mmioButton.addActionListener(ae -> {
  			userOrKernelMode = KERNEL_MODE;
  			homeAddress = Memory.memoryMapBaseAddress;
  			firstAddress = homeAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		textButton.addActionListener(ae -> {
  			userOrKernelMode = USER_MODE;
  			homeAddress = Memory.textBaseAddress;
  			firstAddress = homeAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		dataButton.addActionListener(ae -> {
  			userOrKernelMode = USER_MODE;
  			homeAddress = Memory.dataBaseAddress;
  			firstAddress = homeAddress;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		});
  
  		// NOTE: action listeners for prevButton and nextButton are now in their
  		//       specialized inner classes at the bottom of this listing.  DPS 20 July 2008
  
  	}
  
  	////////////////////////////////////////////////////////////////////////////////////
  	// This will assure that user cannot view memory locations outside the data segment
  	// for selected mode.  For user mode, this means no lower than data segment base,
  	// or higher than user memory boundary.  For kernel mode, this means no lower than
  	// kernel data segment base or higher than kernel memory.  It is called by the
  	// above action listeners.
  	//
  	// lowAddress is lowest desired address to view, it is adjusted if necessary
  	// and returned.
  	//
  	// PrevButton and NextButton are enabled/disabled appropriately.
  	//
  	private int setFirstAddressAndPrevNextButtonEnableStatus(int lowAddress) {
  		final int lowLimit = userOrKernelMode == USER_MODE ? Math.min(Math.min(Memory.textBaseAddress,
  				Memory.dataSegmentBaseAddress), Memory.dataBaseAddress) : Memory.kernelDataBaseAddress;
  		final int highLimit = userOrKernelMode == USER_MODE ? Memory.userHighAddress : Memory.kernelHighAddress;
  		if (lowAddress <= lowLimit) {
  			lowAddress = lowLimit;
  			prevButton.setEnabled(false);
  		} else {
  			prevButton.setEnabled(true);
  		}
  		if (lowAddress >= highLimit - MEMORY_CHUNK_SIZE) {
  			lowAddress = highLimit - MEMORY_CHUNK_SIZE + 1;
  			nextButton.setEnabled(false);
  		} else {
  			nextButton.setEnabled(true);
  		}
  		return lowAddress;
  	}
  
  	/**
  	 * Required by Observer interface. Called when notified by an Observable that we
  	 * are registered with. Observables include: The Simulator object, which lets us
  	 * know when it starts and stops running A delegate of the Memory object, which
  	 * lets us know of memory operations The Simulator keeps us informed of when
  	 * simulated MIPS execution is active. This is the only time we care about
  	 * memory operations.
  	 *
  	 * @param observable The Observable object who is notifying us
  	 * @param obj        Auxiliary object with additional information.
  	 */
  
  	@Override
  	public void update(final Observable observable, final Object obj) {
  		if (observable == mars.simulator.Simulator.getInstance()) {
  			final SimulatorNotice notice = (SimulatorNotice) obj;
  			if (notice.getAction() == SimulatorNotice.SIMULATOR_START) {
  
  				// Simulated MIPS execution starts.  Respond to memory changes if running in timed
  				// or stepped mode.
  				if (notice.getRunSpeed() != RunSpeedPanel.UNLIMITED_SPEED || notice.getMaxSteps() == 1) {
  					Memory.getInstance().addObserver(this);
  					addressHighlighting = true;
  				}
  			} else {
  				// Simulated MIPS execution stops.  Stop responding.
  				Memory.getInstance().deleteObserver(this);
  			}
  		} else if (observable == settings) {
  			// Suspended work in progress. Intended to disable combobox item for text segment. DPS 9-July-2013.
  			//baseAddressSelector.getModel().getElementAt(TEXT_BASE_ADDRESS_INDEX)
  			//*.setEnabled(settings.getBooleanSetting(Settings.SELF_MODIFYING_CODE_ENABLED));
  		} else if (obj instanceof MemoryAccessNotice) {          	// NOTE: observable != Memory.getInstance() because Memory class delegates notification duty.
  			final MemoryAccessNotice access = (MemoryAccessNotice) obj;
  			if (access.getAccessType() == AccessNotice.WRITE) {
  				final int address = access.getAddress();
  				// Use the same highlighting technique as for Text Segment -- see
  				// AddressCellRenderer class below.
  				highlightCellForAddress(address);
  			}
  		}
  	}
  
  	///////////////////////////////////////////////////////////////////////////////
  	// Class defined to address apparent Javax.swing.JComboBox bug: when selection is
  	// is set programmatically using setSelectedIndex() rather than by user-initiated
  	// event (such as mouse click), the text displayed in the JComboBox is not always
  	// updated correctly. Sometimes it is, sometimes updated to incorrect value.
  	// No pattern that I can detect.  Google search yielded many forums addressing
  	// this problem. One suggested solution, a JComboBox superclass overriding
  	// setSelectedIndex to also call selectedItemChanged() did not help.  Only this
  	// solution to extend the model class to call the protected
  	// "fireContentsChanged()" method worked. DPS 25-Jan-2009
  	private class CustomComboBoxModel extends DefaultComboBoxModel {
  
  		/**
  		 *
  		 */
  		private static final long serialVersionUID = -1906248633871059212L;
  
  		public CustomComboBoxModel(final Object[] list) {
  			super(list);
  		}
  
  		private void forceComboBoxUpdate(final int index) {
  			super.fireContentsChanged(this, index, index);
  		}
  	}
  
  	////////////////////////////////////////////////////////////////////////
  	// Class representing memory data table data
  
  	class DataTableModel extends AbstractTableModel {
  
  		/**
  		 *
  		 */
  		private static final long serialVersionUID = 7876732866884891424L;
  		String[] columnNames;
  		Object[][] data;
  
  		public DataTableModel(final Object[][] d, final String[] n) {
  			data = d;
  			columnNames = n;
  		}
  
  		@Override
  		public int getColumnCount() { return columnNames.length; }
  
  		@Override
  		public int getRowCount() { return data.length; }
  
  		@Override
  		public String getColumnName(final int col) {
  			return columnNames[col];
  		}
  
  		@Override
  		public Object getValueAt(final int row, final int col) {
  			return data[row][col];
  		}
  
  		/*
  		 * The cells in the Address column are not editable.
  		* Value cells are editable except when displayed
  		* in ASCII view - don't want to give the impression
  		* that ASCII text can be entered directly because
  		* it can't.  It is possible but not worth the
  		* effort to implement.
  		 */
  		@Override
  		public boolean isCellEditable(final int row, final int col) {
  			//Note that the data/cell address is constant,
  			//no matter where the cell appears onscreen.
  			if (col != ADDRESS_COLUMN && !asciiDisplay) {
  				return true;
  			} else {
  				return false;
  			}
  		}
  
  		/*
  		 * JTable uses this method to determine the default renderer/
  		 * editor for each cell.
  		 */
  		@Override
  		public Class getColumnClass(final int c) {
  			return getValueAt(0, c).getClass();
  		}
  
  		/*
  		 * Update cell contents in table model.  This method should be called
  		* only when user edits cell, so input validation has to be done.  If
  		* value is valid, MIPS memory is updated.
  		 */
  		@Override
  		public void setValueAt(final Object value, final int row, final int col) {
  			int val = 0;
  			int address = 0;
  			try {
  				val = Binary.stringToInt((String) value);
  			} catch (final NumberFormatException nfe) {
  				data[row][col] = "INVALID";
  				fireTableCellUpdated(row, col);
  				return;
  			}
  
  			// calculate address from row and column
  			try {
  				address = Binary.stringToInt((String) data[row][ADDRESS_COLUMN]) + (col - 1) * BYTES_PER_VALUE;  // KENV 1/6/05
  			} catch (final 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.  Also text segment with self-modifying-code setting off.
  				catch (final AddressErrorException aee) {
  					return;
  				}
  			}// end synchronized block
  			final int valueBase = Globals.getGui().getMainPane().getExecutePane().getValueDisplayBase();
  			data[row][col] = NumberDisplayBaseChooser.formatNumber(val, valueBase);
  			fireTableCellUpdated(row, col);
  			return;
  		}
  
  		/*
  		 * Update cell contents in table model.  Does not affect MIPS memory.
  		 */
  		private void setDisplayAndModelValueAt(final Object value, final int row, final int col) {
  			data[row][col] = value;
  			fireTableCellUpdated(row, col);
  		}
  
  		private void printDebugData() {
  			final int numRows = getRowCount();
  			final 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("--------------------------");
  		}
  	}
  
  	// Special renderer capable of highlighting cells by changing background color.
  	// Will set background to highlight color if certain conditions met.
  
  	class AddressCellRenderer extends DefaultTableCellRenderer {
  
  		/**
  		 *
  		 */
  		private static final long serialVersionUID = 5668102592135804041L;
  
  		@Override
  		public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
  				final boolean hasFocus, final int row, final int column) {
  			final JLabel cell = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row,
  					column);
  
  			cell.setHorizontalAlignment(SwingConstants.RIGHT);
  			final int rowFirstAddress = Binary.stringToInt(table.getValueAt(row, ADDRESS_COLUMN).toString());
  			if (settings.getDataSegmentHighlighting() && addressHighlighting
  					&& rowFirstAddress == addressRowFirstAddress && column == addressColumn) {
  				cell.setBackground(settings.getColorSettingByPosition(Settings.DATASEGMENT_HIGHLIGHT_BACKGROUND));
  				cell.setForeground(settings.getColorSettingByPosition(Settings.DATASEGMENT_HIGHLIGHT_FOREGROUND));
  				cell.setFont(settings.getFontByPosition(Settings.DATASEGMENT_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;
  		}
  
  	}
  
  	///////////////////////////////////////////////////////////////////
  	//
  	// 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 static final long serialVersionUID = 2476366627762360905L;
  
  		MyTippedJTable(final DataTableModel m) {
  			super(m);
  		}
  
  		private final String[] columnToolTips = { /* address  */ "Base MIPS memory address for this row of the table.",
  				/* value +0 */ "32-bit value stored at base address for its row.",
  				/* value +n */ "32-bit value stored ", /* value +n */ " bytes beyond base address for its row." };
  
  		//Implement table header tool tips.
  		@Override
  		protected JTableHeader createDefaultTableHeader() {
  			return new JTableHeader(columnModel) {
  
  				@Override
  				public String getToolTipText(final MouseEvent e) {
  					final java.awt.Point p = e.getPoint();
  					final int index = columnModel.getColumnIndexAtX(p.x);
  					final int realIndex = columnModel.getColumn(index).getModelIndex();
  					return realIndex < 2 ? columnToolTips[realIndex]
  							: columnToolTips[2] + (realIndex - 1) * 4 + columnToolTips[3];
  				}
  			};
  		}
  	}
  
  	///////////////////////////////////////////////////////////////////////
  	//
  	//  The Prev button (left arrow) scrolls downward through the
  	//  selected address range.  It is a RepeatButton, which means
  	//  if the mouse is held down on the button, it will repeatedly
  	//  fire after an initial delay.  Allows rapid scrolling.
  	//  DPS 20 July 2008
  	private class PrevButton extends RepeatButton {
  
  		/**
  		 *
  		 */
  		private static final long serialVersionUID = 7956523177113060317L;
  
  		public PrevButton(final Icon ico) {
  			super(ico);
  			setInitialDelay(500);  // 500 milliseconds hold-down before firing
  			setDelay(60);          // every 60 milliseconds after that
  			addActionListener(this);
  		}
  
  		// This one will respond when either timer goes off or button lifted.
  		@Override
  		public void actionPerformed(final ActionEvent ae) {
  			firstAddress -= PREV_NEXT_CHUNK_SIZE;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		}
  	}//////////////////////////////////////////////////////////////////////
  
  	///////////////////////////////////////////////////////////////////////
  	//
  	//  The Next button (right arrow) scrolls upward through the
  	//  selected address range.  It is a RepeatButton, which means
  	//  if the mouse is held down on the button, it will repeatedly
  	//  fire after an initial delay.  Allows rapid scrolling.
  	//  DPS 20 July 2008
  	private class NextButton extends RepeatButton {
  
  		/**
  		 *
  		 */
  		private static final long serialVersionUID = 8171789506235905165L;
  
  		public NextButton(final Icon ico) {
  			super(ico);
  			setInitialDelay(500);  // 500 milliseconds hold-down before firing
  			setDelay(60);          // every 60 milliseconds after that
  			addActionListener(this);
  		}
  
  		// This one will respond when either timer goes off or button lifted.
  		@Override
  		public void actionPerformed(final ActionEvent ae) {
  			firstAddress += PREV_NEXT_CHUNK_SIZE;
  			firstAddress = setFirstAddressAndPrevNextButtonEnableStatus(firstAddress);
  			updateModelForMemoryRange(firstAddress);
  		}
  	}//////////////////////////////////////////////////////////////////////
  
  }
  