
/* 
   The KeyboardApplet class is meant as a starting point for
   writing applets that (1) allow user interaction with the
   applet by pressing keys, and (2) have a separate thread
   that runs continuously whenever the applet has the keyboard
   focus.  (The way it handles the screen is a bit extreme:
   It completely redraws the board whenever it changes.  However,
   this is reasonably easy to manage, and it works well at least
   on fast machines.)
   --David Eck, 7 February 1998
*/

import java.awt.*;
import java.applet.*;

public class KeyboardApplet extends Applet implements Runnable {
   
   // ---------------------- required variables ------------------
   // 
   // The variables in this section are required for the functioning 
   // of the applet.  You can set the values of threadDelay, border_width,
   // back_color, board_color, and hilite_color to customize the applet.
   //     The remaining four variables are set in the init()
   // method, which is defined below.  You should not assign values
   // to these variables, but you can use them in in your methods.

   int threadDelay = 50;  // This applet runs a thread to provide continuous
                          // action (but only while the applet has the input
                          // focus).  The thread calls the method doTimeStep()
                          // over and over.  Between calls to doTimeStep(), it
                          // waits for a time specified by threadDelay.  The
                          // value is in milliseconds.

   int border_width = 10; // All the action in the applet is meant to take
                          // place on a "board" which occupies most of the
                          // applet.  A border of border_width pixels is left
                          // around the board.  The border is shown in the
                          // background color, with a hilite around the
                          // edges when the applet has the input focus;
                          // border_width must be at least 3 to allow room
                          // for the hiliting.


   Color back_color = Color.lightGray;  // Applet background color, used for the border.

   Color hilite_color = Color.cyan;     // Color for hiliting applet when it has the focus.

   Color board_color = Color.green;     // Color of the board.


   int board_width, board_height;  // Height and width of the board on which
                                   //   all the action takes place.

   Image off_screen_board;  // An off-screen copy of the board.  The idea is to
                            // do all the drawing to this image.  It is copied to
                            // the screen whenever the screen needs to be redrawn.
                            // You will probalby use this image only through the
                            // "osg" variable defined next.

   Graphics osg;  // The "off-screen graphics" object that can be used to
                  // draw to the off_screen_board.  Use it in the redrawBoard()
                  // method (or anyplace else where you want to change what
                  // is shown on the board).

 
   // ---------------------- other variables ----------------------------
   //
   // The variables in this section are used in this demo, and can be
   // removed and replaced when you write your own applet.  You need
   // variables to record any information necessary to redraw the
   // board.  There are two types of variables.  Some will be reset
   // in the keyDown() method in response to user actions.  Other
   // variables have to do with the action that is going on in the
   // applet apart from the user events.  Such variables should be
   // recomputed in redrawBoard(), since this is called repeatedly by the
   // applet's "run()" thread.  (The examples here are only of the
   // first type.)

   int squareLeft = 100;  // Position of a square that the user can manipulate
   int squareTop = 100;   // by pressing the arrow keys and certain other keys.

   Color squareColor = Color.red;  // Color of the square


   synchronized void redrawBoard() {

         // This routine is called over-and-over by the applet's
         // "run()" thread (as long as the applet has the keyboard
         // focus).  It should redraw the board and copy it to the
         // screen.  It should also update variables, as necessary,
         // to provide "action" in the applet.

      // First, fill the entire board with the board_color.
      // This effectively erases anything that was there before.
      // (You should probably not change the next two lines.)

      osg.setColor(board_color);
      osg.fillRect(0,0,board_width,board_height);

      // Next, update any variables that should change automatically,
      // as the applet runs, and completely redraw the contents of the board.
      // REPLACE THIS SECTION WITH YOUR OWN COMMANDS.

      osg.setColor(Color.black);  // display the system time
      String time = "System time = " + System.currentTimeMillis();
      osg.drawString(time,5,15);

      osg.setColor(squareColor);  // display the square
      osg.fillRect(squareLeft, squareTop, 40, 40);

      // Finally, copy the modified board onto the screen.
      // Do NOT change this section, unless you really
      // know what you are doing.

      Graphics g = getGraphics();
      g.drawImage(off_screen_board, border_width, border_width, this);
      g.dispose();
   
   }  // end of redrawBoard()



   synchronized public boolean keyDown(Event evt, int key) {

         // This method is called by the system when the user presses a
         // key on the keyboard (but only, of course, if the
         // applet has the keyboard focus).  The "key" parameter
         // tells which key was pressed.  If the user typed an
         // ordinary character, key will be equal to that char.
         // If the user presses certain special keys, such as
         // the arrow keys, the value of key will be one
         // of several constants defined in the Event class.
         // For example, if the user presses the left arrow key,
         // then the value of key is the constant Event.UP.
         //     If the user presses a key that you want to 
         // respond to, you should respond by setting the values
         // of one or more variables, The changes you make will
         // have an effect the next time redrawBoard() is called.

      // The following is sample code.  It lets the user control
      // the position and color of a square by pressing certain 
      // keys.  You can replace or modify this to suit your
      // application.

      if (key == Event.UP) {
         squareTop -= 10;
      }
      else if (key == Event.DOWN) {
         squareTop += 10;
      }
      else if (key == Event.LEFT) {
         squareLeft -= 10;
      }
      else if (key == Event.RIGHT) {
         squareLeft += 10;
      }
      else if (key == 'b' || key == 'B') {
         squareColor = Color.black;
      }
      else if (key == 'w' || key == 'W') {
         squareColor = Color.white;
      }
      else if (key == 'r' || key == 'R') {
         squareColor = Color.red;
      }

      // The following line  is required by the system.
      // Don't change it.

      return true; 

   }  // end of keyDown()


   synchronized public boolean keyUp(Event evt, int key) {
       
        // This method is called when the user releases a key
        // on the keyboard.  It is rarely used.  However, if you
        // want something to keep happening as long as the user
        // holds down a key, you can detect key releases in this
        // method.

      return true; // This is required by the system; don't change it

   } // end of keyUp()



   // ------------- initialize applet and off_screen_board ---------
   // 
   // It should not be necessary to change this.

   public void init() {
      setBackground(back_color);
      board_width = size().width - 2*border_width;
      board_height = size().height - 2*border_width;
      off_screen_board = createImage(board_width,board_height);
      osg = off_screen_board.getGraphics();
      osg.setColor(board_color);
      osg.fillRect(0,0,board_width,board_height);
   }



   //---------------------- handle keyboard focus --------------------
   //
   // It should probably not be necessary to change this.

   private boolean hasFocus = false;  // This variable is set to true
                                      // whenever the applet has the focus.

   synchronized public boolean gotFocus(Event evt, Object what) {
      hasFocus = true;
      notify();
      repaint();
      return true;
   }

   synchronized public boolean lostFocus(Event evt, Object what) {
      hasFocus = false;
      notify();
      repaint();
      return true;
   }


   //----------------------------- paint method --------------------
   //
   // It is probably not be necessary to change this

   synchronized public void paint(Graphics g) {
      if (off_screen_board != null)
         g.drawImage(off_screen_board,border_width,border_width,this);
      if (hasFocus) {  // draw a rectangle around the board to hilite it
         g.setColor(hilite_color);
         g.drawRect(0,0,size().width-1,size().height-1);
         g.drawRect(1,1,size().width-3,size().height-3);
         g.drawRect(2,2,size().width-5,size().height-5);
      }
      else {  // tell user to click on applet so it can get the focus back
         g.setColor(Color.red);
         g.drawString("Click to activate", 25, size().height-30);
      }
   }


   //-------------------- thread stuff and run method -----------------
   //
   // It shoud not be necessary to change this.

   private Thread runner;
   
   public void start() {
      if (runner == null || !runner.isAlive()) {
         runner = new Thread(this);
         runner.start();
      }
      else
         runner.resume();
   }

   public void stop() {
      if (runner != null) {
         if (runner.isAlive())
            runner.suspend();
         else
            runner = null;
      }
   }

   public void destroy() {
      if (runner != null && runner.isAlive())
         runner.stop();
   }

   public void run() {
      while (true) {
         synchronized(this) {
            try { 
                if (hasFocus)          // Note: if you want the thread to run even
                   wait(threadDelay);  // when the applet does not have the input focus,
                else                   // you can replace these four lines with
                   wait();             // Thread.sleep(threadDelay), and delete the gotFocus() and
                                       // lostFocus() methods along with the variable, hasFocus.
            }
            catch (InterruptedException e) {
            }
            redrawBoard();
         }
      }
   }  // end run()

} // end of class KeyboardApplet