package edu.princeton.toy; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import edu.princeton.swing.*; import edu.princeton.toy.lang.*; /** * TSimMemPane is a Scrollable component which displays the memory of a virtualMachine in sim mode. * * @author btsang * @version 7.1 */ public class TSimMemPane extends JPanel implements Scrollable { private static final String CLASS_STRING = TSimMemPane.class.toString(); /** * The command to update the contents of the inputList with the stdin stream of the * virtualMachine. */ public static final String RESCALE_COMMAND = CLASS_STRING + "#rescaleCommand"; /** * The command to update the contents of the inputList with the stdin stream of the * virtualMachine. */ public static final String UPDATE_COMMAND = CLASS_STRING + "#updateCommand"; /** * The minimum scale which the TSimMemPane can be scaled to. */ public static final int MIN_SCALE = 2; /** * The maxmimum scale which the TSimMemPane can be scaled to. */ public static final int MAX_SCALE = Integer.MAX_VALUE; /** * The scale used in determining the preferred size of the TSimMemPane. */ public static final int PREFERRED_SCALE = 14; private static final int CACHE_SIZE = 100; private static final Integer INTEGERS[] = new Integer[CACHE_SIZE]; private static final Font FONT_CACHE[] = new Font[CACHE_SIZE]; private static final int ROW_COUNT = 16; private static final int ROWS_PER_BLOCK = 4; private static final int COLUMN_COUNT = (TVirtualMachine.MEM_COUNT + ROW_COUNT - 1) / ROW_COUNT; private static final int UNSCALED_MINIMUM_PAGE_WIDTH = 11; private static final int UNSCALED_PREFERRED_PAGE_WIDTH = 2 * 6 + 2; private static final int UNSCALED_WIDTH = COLUMN_COUNT * 6 + 1; private static final int UNSCALED_HEIGHT = (ROW_COUNT - 1) / ROWS_PER_BLOCK + ROW_COUNT + 2; private static final Dimension PREFERRED_SCROLLABLE_VIEWPORT_SIZE = new Dimension( UNSCALED_PREFERRED_PAGE_WIDTH * PREFERRED_SCALE, UNSCALED_HEIGHT * PREFERRED_SCALE ); private static final Color SELECTED_CELL_BACKGROUND = new Color(204, 204, 255); private static final Border SELECTED_CELL_BORDER = new LineBorder(new Color(142, 142, 178), 1); /** * Initialize the INTEGERS array. */ static { for (int ctr = 0; ctr < CACHE_SIZE; ctr++) INTEGERS[ctr] = new Integer(ctr); } private int scale; private Rectangle whiteRectangle; private TWord programCtr; private TWord oldProgramCtr; private TWord mem[]; private TWord oldMem[]; private JLabel unselectedCellRenderer; private JLabel selectedCellRenderer; private Listener listener; private TVirtualMachine virtualMachine; private int dragOffset; /** * Creates a new TSimMemPane. */ public TSimMemPane(TVirtualMachine virtualMachine) { super(); setForeground(Color.black); if (virtualMachine == null) throw new NullPointerException(); this.virtualMachine = virtualMachine; mem = new TWord[TVirtualMachine.MEM_COUNT]; oldMem = new TWord[TVirtualMachine.MEM_COUNT]; for (int ctr = 0; ctr < TVirtualMachine.MEM_COUNT; ctr++) mem[ctr] = TWord.UNINITIALIZED_VALUE; programCtr = TVirtualMachine.PC_START; listener = new Listener(); virtualMachine.addChangeListener(listener); unselectedCellRenderer = new JLabel(); unselectedCellRenderer.setOpaque(false); unselectedCellRenderer.setHorizontalAlignment(SwingConstants.CENTER); unselectedCellRenderer.setVerticalAlignment(SwingConstants.CENTER); selectedCellRenderer = new JLabel(); selectedCellRenderer.setOpaque(true); selectedCellRenderer.setBackground(SELECTED_CELL_BACKGROUND); selectedCellRenderer.setBorder(SELECTED_CELL_BORDER); selectedCellRenderer.setHorizontalAlignment(SwingConstants.CENTER); selectedCellRenderer.setVerticalAlignment(SwingConstants.CENTER); scale = -1; whiteRectangle = new Rectangle(); doCommand(RESCALE_COMMAND, INTEGERS[PREFERRED_SCALE]); setMinimumSize( new Dimension( UNSCALED_WIDTH * MIN_SCALE, UNSCALED_HEIGHT * MIN_SCALE ) ); enableEvents(AWTEvent.COMPONENT_EVENT_MASK | AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); } /** * Sets the virtualMachine attached to this TSimMemPane. * * @param virtualMachine The virtualMachine to be attached to this TSimMemPane. */ public void setVirtualMachine(TVirtualMachine virtualMachine) { if (virtualMachine == null) throw new NullPointerException(); if (this.virtualMachine == virtualMachine) return; this.virtualMachine.removeChangeListener(listener); virtualMachine.addChangeListener(listener); this.virtualMachine = virtualMachine; doCommand(UPDATE_COMMAND, Boolean.FALSE); } /** * Returns the virtualMachine attached to this TSimMemPane. * * @return The virtualMachine attached to this TSimMemPane. */ public TVirtualMachine getVirtualMachine() { return virtualMachine; } /** * Performs a command based on the argument. * * @param command A string representing the command. Note that pointer equality (not string * equality) is tested here, so it is important to use the string constants defined in this * class. An IllegalArgumentException will be thrown if the argument is invalid. * @return True iff the command was executed sucessfully. */ public synchronized boolean doCommand(String command, Object extraInfo) { //////////////////////////////////////////////////////////////////////////////////////////// if (command == RESCALE_COMMAND) { int scale = ((Integer)extraInfo).intValue(); if (scale < MIN_SCALE || scale > MAX_SCALE) throw new IllegalArgumentException(); if (scale == this.scale) { repaint(); return false; } Font font; if (scale >= CACHE_SIZE) { font = new Font("Monospaced", Font.BOLD, (int)(0.9 * scale)); } else { if (FONT_CACHE[scale] == null) FONT_CACHE[scale] = new Font("Monospaced", Font.BOLD, (int)(0.9 * scale)); font = FONT_CACHE[scale]; } unselectedCellRenderer.setFont(font); selectedCellRenderer.setFont(font); Dimension preferredSize = getPreferredSize(); preferredSize.width = (UNSCALED_WIDTH + 2) * scale; setPreferredSize(preferredSize); this.scale = scale; repaint(); return true; } //////////////////////////////////////////////////////////////////////////////////////////// if (command == UPDATE_COMMAND) { { // Swap the arrays TWord tempArray[] = mem; mem = oldMem; oldMem = tempArray; } oldProgramCtr = programCtr; programCtr = virtualMachine.getProgramCtr(); virtualMachine.getMem(mem); boolean changed = (oldProgramCtr != programCtr); for (int ctr = 0; !changed && ctr < TVirtualMachine.MEM_COUNT; ctr++) changed = (mem[ctr] != oldMem[ctr]); if (changed) { if (extraInfo != null && ((Boolean)extraInfo).booleanValue()) { scrollToPc(); } else { repaint(); } } return true; } throw new IllegalArgumentException(); } /** * Override paintComponent() to make this TSimMemPane actually paint the memory information. */ public void paintComponent(Graphics g) { Rectangle clip = g.getClipBounds(); int scale = this.scale; // Clear the background g.setColor(getBackground()); g.fillRect(clip.x, clip.y, clip.width, clip.height); int yOffset = whiteRectangle.y; int xOffset = whiteRectangle.x; g.setColor(Color.white); g.fillRect(xOffset, yOffset, whiteRectangle.width, whiteRectangle.height); g.setColor(Color.black); g.drawRect(xOffset, yOffset, whiteRectangle.width, whiteRectangle.height); int columnStart = Math.max(0, ((clip.x - xOffset) / scale - 1) / 6); int columnEnd = Math.min(COLUMN_COUNT - 1, ((clip.x + clip.width - xOffset) / scale - 1) / 6); Color foreground = getForeground(); selectedCellRenderer.setForeground(foreground); unselectedCellRenderer.setForeground(foreground); unselectedCellRenderer.setBackground(getBackground()); selectedCellRenderer.setBounds(0, 0, 5 * scale, scale); unselectedCellRenderer.setBounds(0, 0, 5 * scale, scale); int previousOriginX = 0; int previousOriginY = 0; short pcValue = programCtr.getValue(); for (int colCtr = columnStart; colCtr <= columnEnd; colCtr++) { for (int rowCtr = 0; rowCtr < ROW_COUNT; rowCtr++) { int index = colCtr * ROW_COUNT + rowCtr; if (index < TVirtualMachine.MEM_COUNT) { int currentOriginX = (1 + colCtr * 6) * scale + xOffset; int currentOriginY = (1 + rowCtr / ROWS_PER_BLOCK + rowCtr) * scale + yOffset; g.translate(currentOriginX - previousOriginX, currentOriginY - previousOriginY); if (index == pcValue) { selectedCellRenderer.setText( TWord.HEX_PAIRS[index] + ": " + mem[index].toHexString(false) ); selectedCellRenderer.paint(g); } else { unselectedCellRenderer.setText( TWord.HEX_PAIRS[index] + ": " + mem[index].toHexString(false) ); unselectedCellRenderer.paint(g); } previousOriginX = currentOriginX; previousOriginY = currentOriginY; } } } g.translate(-previousOriginX, -previousOriginY); } /** * Intercept HierarchyEvents to do resize and move the detailPanel when this component is * resized. */ protected void processHierarchyBoundsEvent(HierarchyEvent e) { if (e.getID() == HierarchyEvent.ANCESTOR_RESIZED && e.getChanged() == getParent() && e.getChanged() instanceof JViewport) { deriveScale(); } } /** * Intercept ComponentEvents to do resize and move the memPanel when this component is * resized. */ protected void processComponentEvent(ComponentEvent e) { if (e.getID() == ComponentEvent.COMPONENT_RESIZED) deriveScale(); super.processComponentEvent(e); } /** * Process MouseEvents to mark the beginning of a mouse dragging session. */ protected void processMouseEvent(MouseEvent e) { if (e.getID() == MouseEvent.MOUSE_PRESSED && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { Container parent = getParent(); if (parent instanceof JViewport) dragOffset = e.getX() + getX() + ((JViewport)parent).getViewPosition().x; } super.processMouseEvent(e); } /** * Process MouseEvents to scroll the pane when the mouse is dragged. */ protected void processMouseMotionEvent(MouseEvent e) { if (e.getID() == MouseEvent.MOUSE_DRAGGED && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { Container parent = getParent(); if (parent instanceof JViewport) { JViewport viewport = ((JViewport)parent); Rectangle rectangle = viewport.getViewRect(); int newPosition = dragOffset - (e.getX() + getX()); int paneWidth = getPreferredSize().width; if (newPosition + rectangle.width > paneWidth) newPosition = paneWidth - rectangle.width; if (newPosition < 0) newPosition = 0; viewport.setViewPosition(new Point(newPosition, rectangle.y)); } } super.processMouseEvent(e); } /** * Determines the optimal scale, and then executes the RESCALE_COMMAND. */ private void deriveScale() { int height = getHeight(); int scale = height / UNSCALED_HEIGHT; if (getParent() instanceof JViewport) { int width = ((JViewport)getParent()).getWidth(); scale = Math.min(scale, width / UNSCALED_MINIMUM_PAGE_WIDTH); } if (scale < MIN_SCALE) scale = MIN_SCALE; if (scale > MAX_SCALE) scale = MAX_SCALE; whiteRectangle.x = scale; whiteRectangle.y = height / 2 - UNSCALED_HEIGHT * scale / 2; whiteRectangle.width = scale * UNSCALED_WIDTH; whiteRectangle.height = scale * UNSCALED_HEIGHT; if (scale >= CACHE_SIZE) doCommand(RESCALE_COMMAND, new Integer(scale)); else doCommand(RESCALE_COMMAND, INTEGERS[scale]); } /** * Ensures that the cell representing the memory memory to which the pc points is visible. */ public void scrollToPc() { Container parent = getParent(); if (parent != null && parent instanceof JViewport) { JViewport viewport = (JViewport)parent; Rectangle viewRect = viewport.getViewRect(); Rectangle significantRect; Dimension bounds = getPreferredSize(); int selectedColumn = virtualMachine.getProgramCtr().getValue() / ROW_COUNT; int selectionX = (selectedColumn * 6) * scale + whiteRectangle.x; int selectionWidth = 7 * scale; if (selectionX < viewRect.x) viewRect.x = selectionX; if (selectionX + selectionWidth > viewRect.x + viewRect.width) viewRect.x = selectionX + selectionWidth - viewRect.width; if (viewRect.x + viewRect.width > bounds.width) viewRect.x = bounds.width - viewRect.width; if (viewRect.x < 0) viewRect.x = 0; Point viewPosition = new Point(viewRect.x, viewRect.y); viewport.setViewPosition(viewPosition); parent = parent.getParent(); if (parent != null) parent.repaint(); } else { repaint(); } } /** * Implement scrollable to make the viewport's preferred size that of the page size at * the PREFERRED_SCALE. */ public Dimension getPreferredScrollableViewportSize() { return PREFERRED_SCROLLABLE_VIEWPORT_SIZE; } /** * Implement scrollable to make the unit increment a tenth of a page. */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { switch(orientation) { case SwingConstants.VERTICAL: return visibleRect.height / 10; case SwingConstants.HORIZONTAL: return visibleRect.width / 10; default: throw new IllegalArgumentException("Invalid orientation: " + orientation); } } /** * Implement scrollable to make the block increment a full page. */ public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { switch(orientation) { case SwingConstants.VERTICAL: return visibleRect.height; case SwingConstants.HORIZONTAL: return visibleRect.width; default: throw new IllegalArgumentException("Invalid orientation: " + orientation); } } /** * Implement scrollable to make the InternalTextArea's size the greater of its own size * and its viewport's size. */ public boolean getScrollableTracksViewportWidth() { if (getParent() instanceof JViewport) { return (((JViewport)getParent()).getWidth() > getPreferredSize().width); } return false; } /** * Implement scrollable to make the InternalTextArea's height always equal to that of it's * viewport's. */ public boolean getScrollableTracksViewportHeight() { return true; } /** * The Listener of a TBaseConverterPane pays attention to the changes in the PTextFields, and * fires off changes to the other PTextFields. * * @author btsang * @version 7.1 */ protected class Listener implements ChangeListener { /** * Implement ChangeListener to update the list whenever the virtual machine's state changes. */ public void stateChanged(ChangeEvent e) { doCommand(UPDATE_COMMAND, Boolean.TRUE); } } }