import java.awt.*; public class ConsoleCanvas extends Canvas { // public interface constructor and methods public ConsoleCanvas() { } public final String readLine() { // wait for user to enter a line of input; // Line can only contain characters in the range // ' ' to '~'. return doReadLine(); } public final void addChar(char ch) { // output PRINTABLE character to console putChar(ch); } public final void addCR() { // add a CR to the console putCR(); } public synchronized void clear() { // clear console and return cursor to row 0, column 0. if (OSC == null) return; currentRow = 0; currentCol = 0; OSCGraphics.setColor(Color.white); OSCGraphics.fillRect(4,4,size().width-8,size().height-8); OSCGraphics.setColor(Color.black); repaint(); try { Thread.sleep(25); } catch (InterruptedException e) { } } // focus and key event handlers; not meant to be called excpet by system public boolean keyDown(Event evt, int ch) { doKey((char)ch); return true; } public boolean gotFocus(Event evt, Object what) { doFocus(true); return true; } public boolean lostFocus(Event evt, Object what) { doFocus(false); return true; } // implementation section: protected variables and methods. protected StringBuffer typeAhead = new StringBuffer(); // Characters typed by user but not yet processed; // User can "type ahead" the charcters typed until // they are needed to satisfy a readLine. protected final int maxLineLength = 256; // No lines longer than this are returned by readLine(); // The system effectively inserts a CR after 256 chars // of input without a carriage return. protected int rows, columns; // rows and columns of chars in the console protected int currentRow, currentCol; // current curson position protected Font font; // Font used in console (Courier); All font // data is set up in the doSetup() method. protected int lineHeight; // height of one line of text in the console protected int baseOffset; // distance from top of a line to its baseline protected int charWidth; // width of a character (constant, since a monospaced font is used) protected int leading; // space between lines protected int topOffset; // distance from top of console to top of text protected int leftOffset; // distance from left of console to first char on line protected Image OSC; // off-screen backup for console display (except cursor) protected Graphics OSCGraphics; // graphics context for OSC protected boolean hasFocus = false; // true if this canvas has the input focus protected boolean cursorIsVisible = false; // true if cursor is currently visible private int pos = 0; // exists only for sharing by next two methods public synchronized void clearTypeAhead() { // clears any unprocessed user typing. This is meant only to // be called by ConsolePanel, when a program being run by // console Applet ends. But just to play it safe, pos is // set to -1 as a signal to doReadLine that it should return. typeAhead.setLength(0); pos = -1; notify(); } protected synchronized String doReadLine() { // reads a line of input, up to next CR if (OSC == null) { // If this routine is called before the console has // completely opened, we shouldn't procede; give the // window a chance to open, so that paint() can call doSetup(). try { wait(5000); } // notify() should be set by doSetup() catch (InterruptedException e) {} } if (OSC == null) // If nothing has happened for 5 seconds, we are probably in // trouble, but when the heck, try calling doSetup and proceding anyway. doSetup(); if (!hasFocus) // Make sure canvas has input focus requestFocus(); StringBuffer lineBuffer = new StringBuffer(); // buffer for constructing line from user pos = 0; while (true) { // Read and process chars from the typeAhead buffer until a CR is found. while (pos >= typeAhead.length()) { // If the typeAhead buffer is empty, wait for user to type something cursorBlink(); try { wait(500); } catch (InterruptedException e) { } } if (pos == -1) // means clearTypeAhead was called; return ""; // this is an abnormal return that should not happen if (cursorIsVisible) cursorBlink(); if (typeAhead.charAt(pos) == '\r' || typeAhead.charAt(pos) == '\n') { putCR(); pos++; break; } if (typeAhead.charAt(pos) == 8 || typeAhead.charAt(pos) == 127) { if (lineBuffer.length() > 0) { lineBuffer.setLength(lineBuffer.length() - 1); eraseChar(); } pos++; } else if (typeAhead.charAt(pos) >= ' ' && typeAhead.charAt(pos) < 127) { putChar(typeAhead.charAt(pos)); lineBuffer.append(typeAhead.charAt(pos)); pos++; } else pos++; if (lineBuffer.length() == maxLineLength) { putCR(); pos = typeAhead.length(); break; } } if (pos >= typeAhead.length()) // delete all processed chars from typeAhead typeAhead.setLength(0); else { int len = typeAhead.length(); for (int i = pos; i < len; i++) typeAhead.setCharAt(i - pos, typeAhead.charAt(i)); typeAhead.setLength(len - pos); } return lineBuffer.toString(); // return the string that was entered } protected synchronized void doKey(char ch) { // process key pressed by user typeAhead.append(ch); notify(); } private void putCursor(Graphics g) { // draw the cursor g.drawLine(leftOffset + currentCol*charWidth + 1, topOffset + (currentRow*lineHeight), leftOffset + currentCol*charWidth + 1, topOffset + (currentRow*lineHeight + baseOffset)); } protected synchronized void putChar(char ch) { // draw ch at cursor position and move cursor if (OSC == null) { // If this routine is called before the console has // completely opened, we shouldn't procede; give the // window a chance to open, so that paint() can call doSetup(). try { wait(5000); } // notify() should be set by doSetup() catch (InterruptedException e) {} } if (OSC == null) // If nothing has happened for 5 seconds, we are probably in // trouble, but when the heck, try calling doSetup and proceding anyway. doSetup(); if (currentCol >= columns) putCR(); currentCol++; Graphics g = getGraphics(); g.setColor(Color.black); g.setFont(font); char[] fudge = new char[1]; fudge[0] = ch; g.drawChars(fudge, 0, 1, leftOffset + (currentCol-1)*charWidth, topOffset + currentRow*lineHeight + baseOffset); OSCGraphics.drawChars(fudge, 0, 1, leftOffset + (currentCol-1)*charWidth, topOffset + currentRow*lineHeight + baseOffset); } protected void eraseChar() { // erase char before cursor position and move cursor if (currentCol == 0 && currentRow == 0) return; currentCol--; if (currentCol < 0) { currentRow--; currentCol = columns - 1; } Graphics g = getGraphics(); g.setColor(Color.white); g.fillRect(leftOffset + (currentCol*charWidth), topOffset + (currentRow*lineHeight), charWidth, lineHeight - 1); OSCGraphics.setColor(Color.white); OSCGraphics.fillRect(leftOffset + (currentCol*charWidth), topOffset + (currentRow*lineHeight), charWidth, lineHeight - 1); OSCGraphics.setColor(Color.black); } protected synchronized void putCR() { // move cursor to start of next line, scrolling window if necessary if (OSC == null) { // If this routine is called before the console has // completely opened, we shouldn't procede; give the // window a chance to open, so that paint() can call doSetup(). try { wait(5000); } // notify() should be set by doSetup() catch (InterruptedException e) {} } if (OSC == null) // If nothing has happened for 5 seconds, we are probably in // trouble, but when the heck, try calling doSetup and proceding anyway. doSetup(); currentCol = 0; currentRow++; if (currentRow < rows) return; OSCGraphics.copyArea(leftOffset, topOffset+lineHeight, columns*charWidth, (rows-1)*lineHeight - leading ,0, -lineHeight); OSCGraphics.setColor(Color.white); OSCGraphics.fillRect(leftOffset,topOffset + (rows-1)*lineHeight, columns*charWidth, lineHeight - leading); OSCGraphics.setColor(Color.black); currentRow = rows - 1; repaint(); try { Thread.sleep(25); } // insert some delay to allow screen to redraw catch (InterruptedException e) { } } protected void cursorBlink() { // toggle visibility of cursor (but don't show it if focus has been lost) if (cursorIsVisible) { Graphics g = getGraphics(); g.setColor(Color.white); putCursor(g); cursorIsVisible = false; g.dispose(); } else if (hasFocus) { Graphics g = getGraphics(); g.setColor(Color.black); putCursor(g); cursorIsVisible = true; g.dispose(); } } protected synchronized void doFocus(boolean focus) { // react to gain or loss of focus if (OSC == null) doSetup(); hasFocus = focus; if (hasFocus) // the rest of the routine draws or erases border around canvas OSCGraphics.setColor(Color.cyan); else OSCGraphics.setColor(Color.white); int w = size().width; int h = size().height; for (int i = 0; i < 3; i++) OSCGraphics.drawRect(i,i,w-2*i,h-2*i); OSCGraphics.drawLine(0,h-3,w,h-3); OSCGraphics.drawLine(w-3,0,w-3,h); OSCGraphics.setColor(Color.black); repaint(); try { Thread.sleep(50); } catch (InterruptedException e) { } notify(); } protected void doSetup() { // get font parameters and create OSC int w = size().width; int h = size().height; font = new Font("Courier",Font.PLAIN,getFont().getSize()); FontMetrics fm = getFontMetrics(font); lineHeight = fm.getHeight(); leading = fm.getLeading(); baseOffset = fm.getAscent(); charWidth = fm.charWidth('W'); columns = (w - 12) / charWidth; rows = (h - 12 + leading) / lineHeight; leftOffset = (w - columns*charWidth) / 2; topOffset = (h + leading - rows*lineHeight) / 2; OSC = createImage(w,h); OSCGraphics = OSC.getGraphics(); OSCGraphics.setFont(font); OSCGraphics.setColor(Color.white); OSCGraphics.fillRect(0,0,w,h); OSCGraphics.setColor(Color.black); notify(); } public void update(Graphics g) { paint(g); } public synchronized void paint(Graphics g) { if (OSC == null) doSetup(); g.drawImage(OSC,0,0,this); } }