/* A class that implements basic console-oriented input/output, for use with Console.java and ConsolePanel.java. This class provides the basic character IO. Higher-leve fucntions (reading and writing numbers, booleans, etc) are provided in Console.java and ConolePanel.java. (This vesion of ConsoleCanvas is an udate of an earilier version, rewritten to be compliand with Java 1.1. David Eck; July 17, 1998.) (Modified August 16, 1998 to add the MouseListener interface and a mousePressed method to ConsoleCanvas. The mousePressed method requests the focus. This is necessary for Sun's Java implementation -- though not, apparently for anyone else's. Also added: an isFocusTraversable() method) Minor modifications, February 9, 2000, some glitches in the graphics. */ import java.awt.*; import java.awt.event.*; public class ConsoleCanvas extends Canvas implements FocusListener, KeyListener, MouseListener { // public interface, constructor and methods public ConsoleCanvas() { addFocusListener(this); addKeyListener(this); } 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,getSize().width-8,getSize().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 void keyPressed(KeyEvent evt) { doKey(evt.getKeyChar()); } public void keyReleased(KeyEvent evt) { } public void keyTyped(KeyEvent evt) { } public void focusGained(FocusEvent evt) { doFocus(true); } public void focusLost(FocusEvent evt) { doFocus(false); } public boolean isFocusTraversable() { // Allows the user to move the focus to the canvas // by pressing the tab key. return true; } // Mouse listener methods -- here just to make sure that the canvas // gets the focuse when the user clicks on it. These are meant to // be called only by the system. public void mousePressed(MouseEvent evt) { requestFocus(); } public void mouseReleased(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } // 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); g.dispose(); 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); g.dispose(); 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; Graphics g = getGraphics(); paint(g); g.dispose(); try { Thread.sleep(20); } 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 = getSize().width; int h = getSize().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 = getSize().width; int h = getSize().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); } }