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.*; /** * TSimStdinPane is a Scrollable component which displays the stdin stream of a virtualMachine in * sim mode. * * @author btsang * @version 7.1 */ public class TSimStdinPane extends JPanel implements Scrollable { private static final String CLASS_STRING = TSimStdinPane.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 TSimStdinPane can be scaled to. */ public static final int MIN_SCALE = 2; /** * The maxmimum scale which the TSimStdinPane can be scaled to. */ public static final int MAX_SCALE = Integer.MAX_VALUE; /** * The scale used in determining the preferred size of the TSimStdinPane. */ 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 UNSCALED_MINIMUM_PAGE_WIDTH = 9; private static final int UNSCALED_PREFERRED_PAGE_WIDTH = 2 * 5 + 2; 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 columnCount; private int unscaledWidth; private int scale; private Rectangle whiteRectangle; TWordBuffer unconsumedStdin, consumedStdin; TWordBuffer oldUnconsumedStdin, oldConsumedStdin; private JLabel disabledCellRenderer; private JLabel unselectedCellRenderer; private JLabel selectedCellRenderer; private Listener listener; private TVirtualMachine virtualMachine; private int dragOffset; /** * Creates a new TSimStdinPane. */ public TSimStdinPane(TVirtualMachine virtualMachine) { super(); setForeground(Color.black); if (virtualMachine == null) throw new NullPointerException(); this.virtualMachine = virtualMachine; unconsumedStdin = new TWordBuffer(); consumedStdin = new TWordBuffer(); oldUnconsumedStdin = new TWordBuffer(); oldConsumedStdin = new TWordBuffer(); listener = new Listener(); virtualMachine.addChangeListener(listener); disabledCellRenderer = new JLabel(); disabledCellRenderer.setOpaque(false); disabledCellRenderer.setEnabled(false); disabledCellRenderer.setHorizontalAlignment(SwingConstants.CENTER); disabledCellRenderer.setVerticalAlignment(SwingConstants.CENTER); 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( 1 * 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 TSimStdinPane. * * @param virtualMachine The virtualMachine to be attached to this TSimStdinPane. */ 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 TSimStdinPane. * * @return The virtualMachine attached to this TSimStdinPane. */ 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]; } disabledCellRenderer.setFont(font); unselectedCellRenderer.setFont(font); selectedCellRenderer.setFont(font); Dimension preferredSize = getPreferredSize(); preferredSize.width = (unscaledWidth + 2) * scale; setPreferredSize(preferredSize); this.scale = scale; repaint(); return true; } //////////////////////////////////////////////////////////////////////////////////////////// if (command == UPDATE_COMMAND) { { // Swap the buffers TWordBuffer tempBuffer = oldConsumedStdin; oldConsumedStdin = consumedStdin; consumedStdin = tempBuffer; tempBuffer = oldUnconsumedStdin; oldUnconsumedStdin = unconsumedStdin; unconsumedStdin = tempBuffer; } virtualMachine.getConsumedStdin(consumedStdin); virtualMachine.getUnconsumedStdin(unconsumedStdin); int columnCount = Math.max( 2, (unconsumedStdin.getSize() + consumedStdin.getSize()) / ROW_COUNT + 1 ); if (this.columnCount != columnCount) { this.columnCount = columnCount; unscaledWidth = columnCount * 5 + 1; whiteRectangle.width = scale * unscaledWidth; Dimension preferredSize = getPreferredSize(); preferredSize.width = (unscaledWidth + 2) * scale; setPreferredSize(preferredSize); revalidate(); } boolean consumedStdinUnchanged = consumedStdin.equals(oldConsumedStdin); if (!consumedStdinUnchanged || !unconsumedStdin.equals(oldUnconsumedStdin)) { if (extraInfo != null && ((Boolean)extraInfo).booleanValue()) { scrollToFrontier( !consumedStdinUnchanged || unconsumedStdin.getSize() <= oldUnconsumedStdin.getSize() ); } else { repaint(); } } return true; } throw new IllegalArgumentException(); } /** * Override paintComponent() to make this TSimStdinPane 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) / 5); int columnEnd = Math.min(columnCount - 1, ((clip.x + clip.width - xOffset) / scale - 1) / 5); Color foreground = getForeground(); selectedCellRenderer.setForeground(foreground); unselectedCellRenderer.setForeground(foreground); unselectedCellRenderer.setBackground(Color.white); disabledCellRenderer.setBackground(Color.white); disabledCellRenderer.setBounds(0, 0, 4 * scale, scale); selectedCellRenderer.setBounds(0, 0, 4 * scale, scale); unselectedCellRenderer.setBounds(0, 0, 4 * scale, scale); int previousOriginX = 0; int previousOriginY = 0; int consumedStdinSize = consumedStdin.getSize(); int totalStdinSize = consumedStdinSize + unconsumedStdin.getSize(); for (int colCtr = columnStart; colCtr <= columnEnd; colCtr++) { for (int rowCtr = 0; rowCtr < ROW_COUNT; rowCtr++) { int index = colCtr * ROW_COUNT + rowCtr; if (index < consumedStdinSize) { int currentOriginX = (1 + colCtr * 5) * scale + xOffset; int currentOriginY = (1 + rowCtr / ROWS_PER_BLOCK + rowCtr) * scale + yOffset; g.translate(currentOriginX - previousOriginX, currentOriginY - previousOriginY); disabledCellRenderer.setText( consumedStdin.getWord(index).toHexString(false) ); disabledCellRenderer.paint(g); previousOriginX = currentOriginX; previousOriginY = currentOriginY; } else if (index < totalStdinSize) { int currentOriginX = (1 + colCtr * 5) * scale + xOffset; int currentOriginY = (1 + rowCtr / ROWS_PER_BLOCK + rowCtr) * scale + yOffset; g.translate(currentOriginX - previousOriginX, currentOriginY - previousOriginY); if (index == consumedStdinSize) { selectedCellRenderer.setText( unconsumedStdin.getWord(index - consumedStdinSize).toHexString(false) ); selectedCellRenderer.paint(g); } else { unselectedCellRenderer.setText( unconsumedStdin.getWord(index - consumedStdinSize).toHexString(false) ); unselectedCellRenderer.paint(g); } previousOriginX = currentOriginX; previousOriginY = currentOriginY; } else if (index == totalStdinSize) { int currentOriginX = (1 + colCtr * 5) * scale + xOffset; int currentOriginY = (1 + rowCtr / ROWS_PER_BLOCK + rowCtr) * scale + yOffset; g.translate(currentOriginX - previousOriginX, currentOriginY - previousOriginY); if (index == consumedStdinSize) { selectedCellRenderer.setText("Prompt"); selectedCellRenderer.paint(g); } else { unselectedCellRenderer.setText("Prompt"); unselectedCellRenderer.paint(g); } previousOriginX = currentOriginX; previousOriginY = currentOriginY; } else { int currentOriginX = (1 + colCtr * 5) * scale + xOffset; int currentOriginY = (1 + rowCtr / ROWS_PER_BLOCK + rowCtr) * scale + yOffset; g.translate(currentOriginX - previousOriginX, currentOriginY - previousOriginY); disabledCellRenderer.setText("-"); disabledCellRenderer.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 * unscaledWidth; 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 stdin word at the frontier of either the consumed * stdin list or the unconsumed stdin list is visible. */ public void scrollToFrontier(boolean consumedFrontier) { int selectedColumn; if (consumedFrontier) selectedColumn = consumedStdin.getSize() / ROW_COUNT; else selectedColumn = (consumedStdin.getSize() + unconsumedStdin.getSize()) / ROW_COUNT; scrollRectToVisible( new Rectangle( (selectedColumn * 6) * scale + whiteRectangle.x, 0, 7 * scale, getHeight() ) ); 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); } } }