Console Applet

<< ClassQuizScorer | OtherProjectsTrailIndex | Questions 20 >>

To make an applet behave like command line console application, add these 3 classes to your project and make your class a subclass of ConsoleApplet, overriding the program() method. Use console.putln() instead of System.out.println() and console.getln() instead of scanner.nextLine()

ConsoleApplet.java


/*  The ConsoleApplet provides a basis for writing applets that simulate
    line-oriented console-type input/output.  The class ConsoleCanvas is
    used to provide the I/O routines; these routines type output into
    the ConsoleCanvas and read input typed there by the user.  See the
    files CanvasPanel.java and ConsoleCanvas.java for information.

    A console applet is set up to run one particular program.  The applet
    includes a button that the user presses to run the program (or to
    abort it when it is running).  It also includes a label, which by default
    is "Java I/O Console".  The default program, defined in this file,
    is a simple Hello World program.

    To write a new console applet, with a more interesting program, you
    should create a subclass of ConsoleApplet.  The subclass should
    override the method  program() , which represents the program to 
    be run.  The program should use the instance variable, console, to
    do input/output.  For example, console.putln("HelloWorld.")
    or N=console.getInt().  All the public methods of class ConsolePanel
    can be called in the program.

    (Of course, you could also just modify this file, instead of
    writing a subclass.)

    David Eck
    Department of Mathematics and Computer Science
    Hobart and William Smith Colleges
    Geneva, NY 14456
    eck@hws.edu

    July 17, 1998: Program was modified to be compliant with Java 1.1.
    December 9, 1997:  This is a modification of a version written
    on August 2, 1996.  (Subclasses of previous versions of the old applet
    should still work with this new version.)

*/



import java.awt.*;
import java.awt.event.*;

public class ConsoleApplet extends java.applet.Applet
                           implements Runnable, ActionListener {

   protected String title = "Java Console I/O";  // (Used for compatibility with previous versions of Console Applet)

   protected String getTitle() {
       // Return a label to appears over the console;
       // If you want to change the label, override this
       // method to return a different string.
      return title;
   }

   protected  ConsolePanel console;  // console for use in program()

   protected void program() {  
          // The console-type program; override this in your sub-class
          // to be the program that you want your applet to run.
          // Use the variable "console", which is already defined,
          // to do inuput/output in your program.
      console.putln("Hello World!");
   }


   // The remainder of this file consists of implementation details that
   // you don't have to understand in order to write your own console applets.

   private Button runButton;  // user presses this to run the program

   private Thread programThread = null;     // thread for running the program; the run()
                                            //    method calls program()
   private boolean programRunning = false;
   private boolean firstTime = true;  //    set to false the first time program is run

   public void run() {   // just run the program()
      programRunning = true;
      program();
      programRunning = false;
      stopProgram();
   }

   synchronized private void startProgram() {
      runButton.setLabel("Abort Program");
      if (!firstTime) {
         console.clear();
         try { Thread.sleep(300); }  // some delay before restarting the program
         catch (InterruptedException e) { }
      }
      firstTime = false;
      programThread = new Thread(this);
      programThread.start();
   }

   synchronized private void stopProgram() {
      if (programRunning) {
         programThread.stop();
         try { programThread.join(1000); }
         catch (InterruptedException e) { }
      }
      console.clearBuffers();
      programThread = null;
      programRunning = false;
      runButton.setLabel("Run Again");
      runButton.requestFocus();
   }

   public void init() {

      setBackground(Color.black);

      setLayout(new BorderLayout(2,2));
      console = new ConsolePanel();
      add("Center",console);

      Panel temp = new Panel();
      temp.setBackground(Color.white);
      Label lab = new Label(getTitle());
      temp.add(lab);
      lab.setForeground(new Color(180,0,0));
      add("North", temp);

      runButton = new Button("Run the Program");
      temp = new Panel();
      temp.setBackground(Color.white);
      temp.add(runButton);
      runButton.addActionListener(this);
      add("South",temp);

   }

   public Insets getInsets() {
      return new Insets(2,2,2,2);
   }

   public void stop() {
      if (programRunning) {
         stopProgram();
         console.putln();
         console.putln("*** PROGRAM HALTED");
      }
   }

   synchronized public void actionPerformed(ActionEvent evt) { 
            // Only possible action is a click on the button.
            // If program is running, stop it; otherwise, run it.
      if (programThread != null) {
         stopProgram();
         console.putln();
         console.putln("*** PROGRAM ABORTED BY USER");
      }
      else
         startProgram();
   }

}

ConsolePanel.java

/*
    The file defines a class ConsolePanel.  Objects of type
    ConsolePanel can be used for simple input/output exchanges with
    the user.  Various routines are provided for reading and writing
    values of various types from the output.  (This class gives all
    the I/O behavior of another class, Console, that represents a
    separate window for doing console-style I/O.)

    This class is dependent on another class, ConsoleCanvas.

    Note that when the console has the input focus, it is outlined with
    a bright blue border.  If, in addition, the console is waiting for
    user input, then there will be a blinking cursor.  If the console
    is not outlined in light blue, the user has to click on it before
    any input will be accepted.

    This is an update an earlier version of the same class,
    rewritten to use realToString() for output of floating point
    numbers..

    Written by:  David Eck
                 Department of Mathematics and Computer Science
                 Hobart and William Smith Colleges
                 Geneva, NY 14456
                 Email:  eck@hws.edu
                 WWW:  http://math.hws.edu/eck/

    July 17, 1998.

*/


import java.awt.*;

public class ConsolePanel extends Panel {

   // ***************************** Constructors *******************************

   public ConsolePanel() {   // default constructor just provides default window title and size
      setBackground(Color.white);
      setLayout(new BorderLayout(0,0));
      canvas = new ConsoleCanvas();
      add("Center",canvas);
   }


   public void clear() {  // clear all characters from the canvas
      canvas.clear();
   }


   // *************************** I/O Methods *********************************

         // Methods for writing the primitive types, plus type String,
         // to the console window, with no extra spaces.
         //
         // Note that the real-number data types, float
         // and double, a rounded version is output that will
         // use at most 10 or 11 characters.  If you want to
         // output a real number with full accuracy, use
         // "con.put(String.valueOf(x))", for example.

   public void put(int x)     { put(x,0); }   // Note: also handles byte and short!
   public void put(long x)    { put(x,0); }
   public void put(double x)  { put(x,0); }   // Also handles float.
   public void put(char x)    { put(x,0); }
   public void put(boolean x) { put(x,0); }
   public void put(String x)  { put(x,0); }


         // Methods for writing the primitive types, plus type String,
         // to the console window,followed by a carriage return, with
         // no extra spaces.

   public void putln(int x)      { put(x,0); newLine(); }   // Note: also handles byte and short!
   public void putln(long x)     { put(x,0); newLine(); }
   public void putln(double x)   { put(x,0); newLine(); }   // Also handles float.
   public void putln(char x)     { put(x,0); newLine(); }
   public void putln(boolean x)  { put(x,0); newLine(); }
   public void putln(String x)   { put(x,0); newLine(); }


         // Methods for writing the primitive types, plus type String,
         // to the console window, with a minimum field width of w,
         // and followed by a carriage  return.
         // If outut value is less than w characters, it is padded
         // with extra spaces in front of the value.

   public void putln(int x, int w)     { put(x,w); newLine(); }   // Note: also handles byte and short!
   public void putln(long x, int w)    { put(x,w); newLine(); }
   public void putln(double x, int w)  { put(x,w); newLine(); }   // Also handles float.
   public void putln(char x, int w)    { put(x,w); newLine(); }
   public void putln(boolean x, int w) { put(x,w); newLine(); }
   public void putln(String x, int w)  { put(x,w); newLine(); }


          // Method for outputting a carriage return

   public void putln() { newLine(); }


         // Methods for writing the primitive types, plus type String,
         // to the console window, with minimum field width w.

   public void put(int x, int w)     { dumpString(String.valueOf(x), w); }   // Note: also handles byte and short!
   public void put(long x, int w)    { dumpString(String.valueOf(x), w); }
   public void put(double x, int w)  { dumpString(realToString(x), w); }   // Also handles float.
   public void put(char x, int w)    { dumpString(String.valueOf(x), w); }
   public void put(boolean x, int w) { dumpString(String.valueOf(x), w); }
   public void put(String x, int w)  { dumpString(x, w); }


         // Methods for reading in the primitive types, plus "words" and "lines".
         // The "getln..." methods discard any extra input, up to and including
         //    the next carriage return.
         // A "word" read by getlnWord() is any sequence of non-blank characters.
         // A "line" read by getlnString() or getln() is everything up to next CR;
         //    the carriage return is not part of the returned value, but it is
         //    read and discarded.
         // Note that all input methods except getAnyChar(), peek(), the ones for lines
         //    skip past any blanks and carriage returns to find a non-blank value.
         // getln() can return an empty string; getChar() and getlnChar() can 
         //    return a space or a linefeed ('\n') character.
         // peek() allows you to look at the next character in input, without
         //    removing it from the input stream.  (Note that using this
         //    routine might force the user to enter a line, in order to
         //    check what the next character.)
         // Acceptable boolean values are the "words": true, false, t, f, yes,
         //    no, y, n, 0, or 1;  uppercase letters are OK.
         // None of these can produce an error; if an error is found in input,
         //    the user is forced to re-enter.
         // Available input routines are:
         //
         //            getByte()      getlnByte()    getShort()     getlnShort()
         //            getInt()       getlnInt()     getLong()      getlnLong()
         //            getFloat()     getlnFloat()   getDouble()    getlnDouble()
         //            getChar()      getlnChar()    peek()         getAnyChar()
         //            getWord()      getlnWord()    getln()        getString()    getlnString()
         //
         // (getlnString is the same as getln and is onlyprovided for consistency.)

   public byte getlnByte()       { byte x=getByte();       emptyBuffer();  return x; }
   public short getlnShort()     { short x=getShort();     emptyBuffer();  return x; }
   public int getlnInt()         { int x=getInt();         emptyBuffer();  return x; }
   public long getlnLong()       { long x=getLong();       emptyBuffer();  return x; }
   public float getlnFloat()     { float x=getFloat();     emptyBuffer();  return x; }
   public double getlnDouble()   { double x=getDouble();   emptyBuffer();  return x; }
   public char getlnChar()       { char x=getChar();       emptyBuffer();  return x; }
   public boolean getlnBoolean() { boolean x=getBoolean(); emptyBuffer();  return x; }
   public String getlnWord()     { String x=getWord();     emptyBuffer();  return x; }
   public String getlnString()   { return getln(); }  // same as getln()
   public String getln() {
      StringBuffer s = new StringBuffer(100);
      char ch = readChar();
      while (ch != '\n') {
         s.append(ch);
         ch = readChar();
      }
      return s.toString();
   }


   public byte getByte()   { return (byte)readInteger(-128L,127L); }
   public short getShort() { return (short)readInteger(-32768L,32767L); }   
   public int getInt()     { return (int)readInteger((long)Integer.MIN_VALUE, (long)Integer.MAX_VALUE); }
   public long getLong()   { return readInteger(Long.MIN_VALUE, Long.MAX_VALUE); }

   public char getAnyChar(){ return readChar(); }
   public char peek()      { return lookChar(); }

   public char getChar() {  // skip spaces & cr's, then return next char
      char ch = lookChar();
      while (ch == ' ' || ch == '\n') {
         readChar();
         if (ch == '\n')
            dumpString("? ",0);
         ch = lookChar();
      }
      return readChar();
   }

   public float getFloat() {  // can return positive or negative infinity
      float x = 0.0F;
      while (true) {
         String str = readRealString();
         if (str.equals("")) {
             errorMessage("Illegal floating point input.",
                          "Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
         }
         else {
            Float f = null;
            try { f = Float.valueOf(str); }
            catch (NumberFormatException e) {
               errorMessage("Illegal floating point input.",
                            "Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
               continue;
            }
            if (f.isInfinite()) {
               errorMessage("Floating point input outside of legal range.",
                            "Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
               continue;
            }
            x = f.floatValue();
            break;
         }
      }
      return x;
   }

   public double getDouble() {
      double x = 0.0;
      while (true) {
         String str = readRealString();
         if (str.equals("")) {
             errorMessage("Illegal floating point input",
                          "Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
         }
         else {
            Double f = null;
            try { f = Double.valueOf(str); }
            catch (NumberFormatException e) {
               errorMessage("Illegal floating point input",
                            "Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
               continue;
            }
            if (f.isInfinite()) {
               errorMessage("Floating point input outside of legal range.",
                            "Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
               continue;
            }
            x = f.doubleValue();
            break;
         }
      }
      return x;
   }

   public String getWord() {
      char ch = lookChar();
      while (ch == ' ' || ch == '\n') {
         readChar();
         if (ch == '\n')
            dumpString("? ",0);
         ch = lookChar();
      }
      StringBuffer str = new StringBuffer(50);
      while (ch != ' ' && ch != '\n') {
         str.append(readChar());
         ch = lookChar();
      }
      return str.toString();
   }

   public boolean getBoolean() {
      boolean ans = false;
      while (true) {
         String s = getWord();
         if ( s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t") ||
                 s.equalsIgnoreCase("yes")  || s.equalsIgnoreCase("y") ||
                 s.equals("1") ) {
              ans = true;
              break;
          }
          else if ( s.equalsIgnoreCase("false") || s.equalsIgnoreCase("f") ||
                 s.equalsIgnoreCase("no")  || s.equalsIgnoreCase("n") ||
                 s.equals("0") ) {
              ans = false;
              break;
          }
          else
             errorMessage("Illegal boolean input value.",
                          "one of:  true, false, t, f, yes, no, y, n, 0, or 1");
      }
      return ans;
   }

   // ***************** Everything beyond this point is private *******************

   // ********************** Utility routines for input/output ********************

   private ConsoleCanvas canvas;  // the canvas where I/O is displayed
   private String buffer = null;  // one line read from input
   private int pos = 0;           // position next char in input line that has
                                  //      not yet been processed


   private String readRealString() {   // read chars from input following syntax of real numbers
      StringBuffer s=new StringBuffer(50);
      char ch=lookChar();
      while (ch == ' ' || ch == '\n') {
          readChar();
          if (ch == '\n')
             dumpString("? ",0);
          ch = lookChar();
      }
      if (ch == '-' || ch == '+') {
          s.append(readChar());
          ch = lookChar();
          while (ch == ' ') {
             readChar();
             ch = lookChar();
          }
      }
      while (ch >= '0' && ch <= '9') {
          s.append(readChar());
          ch = lookChar();
      }
      if (ch == '.') {
         s.append(readChar());
         ch = lookChar();
         while (ch >= '0' && ch <= '9') {
             s.append(readChar());
             ch = lookChar();
         }
      }
      if (ch == 'E' || ch == 'e') {
         s.append(readChar());
         ch = lookChar();
         if (ch == '-' || ch == '+') {
             s.append(readChar());
             ch = lookChar();
         }
         while (ch >= '0' && ch <= '9') {
             s.append(readChar());
             ch = lookChar();
         }
      }
      return s.toString();
   }

   private long readInteger(long min, long max) {  // read long integer, limited to specified range
      long x=0;
      while (true) {
         StringBuffer s=new StringBuffer(34);
         char ch=lookChar();
         while (ch == ' ' || ch == '\n') {
             readChar();
             if (ch == '\n')
                dumpString("? ",0);
             ch = lookChar();
         }
         if (ch == '-' || ch == '+') {
             s.append(readChar());
             ch = lookChar();
             while (ch == ' ') {
                readChar();
                ch = lookChar();
             }
         }
         while (ch >= '0' && ch <= '9') {
             s.append(readChar());
             ch = lookChar();
         }
         if (s.equals("")){
             errorMessage("Illegal integer input.",
                          "Integer in the range " + min + " to " + max);
         }
         else {
             String str = s.toString();
             try { 
                x = Long.parseLong(str);
             }
             catch (NumberFormatException e) {
                errorMessage("Illegal integer input.",
                             "Integer in the range " + min + " to " + max);
                continue;
             }
             if (x < min || x > max) {
                errorMessage("Integer input outside of legal range.",
                             "Integer in the range " + min + " to " + max);
                continue;
             }
             break;
         }
      }
      return x;
   }

   private static String realToString(double x) {
         // Goal is to get a reasonable representation of x in at most
         // 10 characters, or 11 characters if x is negative.
      if (Double.isNaN(x))
         return "undefined";
      if (Double.isInfinite(x))
         if (x < 0)
            return "-INF";
         else
            return "INF";
      if (Math.abs(x) <= 5000000000.0 && Math.rint(x) == x)
         return String.valueOf( (long)x );
      String s = String.valueOf(x);
      if (s.length() <= 10)
         return s;
      boolean neg = false;
      if (x < 0) {
         neg = true;
         x = -x;
         s = String.valueOf(x);
      }
      if (x >= 0.00005 && x <= 50000000 && (s.indexOf('E') == -1 && s.indexOf('e') == -1)) {  // trim x to 10 chars max
         s = round(s,10);
         s = trimZeros(s);
      }
      else if (x > 1) { // construct exponential form with positive exponent
          long power = (long)Math.floor(Math.log(x)/Math.log(10));
          String exp = "E" + power;
          int numlength = 10 - exp.length();
          x = x / Math.pow(10,power);
          s = String.valueOf(x);
          s = round(s,numlength);
          s = trimZeros(s);
          s += exp;
      }
      else { // constuct exponential form
          long power = (long)Math.ceil(-Math.log(x)/Math.log(10));
          String exp = "E-" + power;
          int numlength = 10 - exp.length();
          x = x * Math.pow(10,power);
          s = String.valueOf(x);
          s = round(s,numlength);
          s = trimZeros(s);
          s += exp;
      }
      if (neg)
         return "-" + s;
      else
         return s;
   }

   private static String trimZeros(String num) {  // used by realToString
     if (num.indexOf('.') >= 0 && num.charAt(num.length() - 1) == '0') {
        int i = num.length() - 1;
        while (num.charAt(i) == '0')
           i--;
        if (num.charAt(i) == '.')
           num = num.substring(0,i);
        else
           num = num.substring(0,i+1);
     }
     return num;
   }

   private static String round(String num, int length) {  // used by realToString
      if (num.indexOf('.') < 0)
         return num;
      if (num.length() <= length)
         return num;
      if (num.charAt(length) >= '5' && num.charAt(length) != '.') {
         char[] temp = new char[length+1];
         int ct = length;
         boolean rounding = true;
         for (int i = length-1; i >= 0; i--) {
            temp[ct] = num.charAt(i); 
            if (rounding && temp[ct] != '.') {
               if (temp[ct] < '9') {
                  temp[ct]++;
                  rounding = false;
               }
               else
                  temp[ct] = '0';
            }
            ct--;
         }
         if (rounding) {
            temp[ct] = '1';
            ct--;
         }
         // ct is -1 or 0
         return new String(temp,ct+1,length-ct);
      }
      else 
         return num.substring(0,length);

   }

   private void dumpString(String str, int w) {   // output string to console
      for (int i=str.length(); i<w; i++)
         canvas.addChar(' ');
      for (int i=0; i<str.length(); i++)
         if ((int)str.charAt(i) >= 0x20 && (int)str.charAt(i) != 0x7F)  // no control chars or delete
            canvas.addChar(str.charAt(i));
         else if (str.charAt(i) == '\n' || str.charAt(i) == '\r')
            newLine();
   }

   private void errorMessage(String message, String expecting) {
                  // inform user of error and force user to re-enter.
       newLine();
       dumpString("  *** Error in input: " + message + "\n", 0);
       dumpString("  *** Expecting: " + expecting + "\n", 0);
       dumpString("  *** Discarding Input: ", 0);
       if (lookChar() == '\n')
          dumpString("(end-of-line)\n\n",0);
       else {
          while (lookChar() != '\n')
             canvas.addChar(readChar());
          dumpString("\n\n",0);
       }
       dumpString("Please re-enter: ", 0);
       readChar();  // discard the end-of-line character
   }

   private char lookChar() {  // return next character from input
      if (buffer == null || pos > buffer.length())
         fillBuffer();
      if (pos == buffer.length())
         return '\n';
      return buffer.charAt(pos);
   }

   private char readChar() {  // return and discard next character from input
      char ch = lookChar();
      pos++;
      return ch;
   }

   private void newLine() {   // output a CR to console
      canvas.addCR();
   }

   private void fillBuffer() {    // Wait for user to type a line and press return,
                                  // and put the typed line into the buffer.
      buffer = canvas.readLine();
      pos = 0;
   }

   private void emptyBuffer() {   // discard the rest of the current line of input
      buffer = null;
   }

   public void clearBuffers() {   // I expect this will only be called by
                                  // CanvasApplet when a program ends.  It should
                                  // not be called in the middle of a running program.
      buffer = null;
      canvas.clearTypeAhead();
   }


} // end of class Console

ConsoleCanvas.java


/* 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);
   }


}