
/*
    The KeyboardAnimation applet provides a generic framework for applets
    both display an animation and respond to keyboard events.  The animation
    runs only when the applet has the keyboard focus and can respond to 
    key presses.  The appearance of the applet changes, depending on whether
    it has the keyboard focus.  Note that each time a new frame of the 
    animation is to be displayed, it is drawn completely from scratch.
    When the applet has the keyboard focus, a cyan border is drawn around
    it.  When it does not have the keyboard focus, the border is in
    the applet's background color and a message "Click to activate" is
    displayed in the applet's foreground color and font.

    This class would be appropriate, for example, as a basis for a typical
    arcade game, such as Space Invaders.  (Except that the preformance
    won't be so good.)
    
    To use this framework, define a subclass of KeyboardAnimationApplet and
    override the drawFrame() method.  This method is responsible for drawing
    one frame of the animation.  If you need to some initialization at the
    time the applet is created, override the doInitialization() method.
    This method is called once when the applet is created.  (You should
    NOT override the standard applet methods, init(), start(), stop(), or
    destroy() unless you call the inherited versions from this class.  These
    routines perform important functions in this class.)
    
    In this class, the applet is already set up to "listen" for keyboard
    events.  To make your applet respond to keyboard events, you should
    override one or more of the methods keyPressed(), keyReleased(),
    and keyTyped().  (The applet also listens for MouseEvents, and you
    can override the mouse handling events if you want.  But if you do
    override mousePressed(), be sure to call requestFocus() in that
    routine.)
    
    To respond to key presses, you should have some instance variables
    that affect the image drawn.  Change these variables in the keyPressed,
    keyReleased, or keyTyped methods.
    
    (Alternatively, instead of defining a subclass, you could copy this
    file, change its name and the name of the class, and edit it.)
    
    (This applet is requires Java 1.1, since it uses Java 1.1 style event
    handling.)
    
    David Eck
    Department of Mathematics and Computer Science
    Hobart and William Smith Colleges
    Geneva, NY 14456
    eck@hws.edu
    
    October 2, 1998
    Small modifications 18 February 2000
*/

import java.awt.*;
import java.awt.event.*;

public class KeyboardAnimationApplet extends java.applet.Applet 
                 implements Runnable, KeyListener, FocusListener, MouseListener {
                 
   protected void doInitialization(int width, int height) {
         // This routine is called once when the applet is first created.
         // You can override it to do initialzation of your instance
         // variables.  It's also a good place to call setFrameCount()
         // and setMillisecondsPerFrame(), if you want to customize these
         // values.  The parameters tell the size of the drawing area
         // at the time the applet is created.
   }

   protected void drawFrame(Graphics g, int width, int height) {
         // This routine should be overridden in any subclass of KeyboardAnimationApplet.
         // It is responsible for drawing one frame of the animation.  The frame
         // is drawn to the graphics context g.  The parameters width and height
         // give the size of the drawing area.  drawFrame() should begin by
         // filling the drawing area with a background color (as is done in this
         // version of drawFrame).  It should then draw the contents of the
         // frame.  The routine can call getFrameNumber() to dermine which frame
         // it is supposed to draw.  It can call getElapsedTime() to find out
         // how long the animation has been running, in milliseconds.
         // Note that this routine should not take a long time to execute!
         // As an example, the elapsed number of seconds and the frame number
         // are output.
      g.setColor(Color.lightGray);
      g.fillRect(0,0,width,height);
      g.setColor(Color.black);
      g.drawString("Elapsed Time:  " + (getElapsedTime()/1000),10,20);
      g.drawString("Frame Number:  " + (getFrameNumber()),10,35);
   }
   
   public void keyTyped(KeyEvent evt) { 
         // Method to respond when the user types a character.  Use
         // char key = evt.getKeyChar() to find out which character
         // was typed.  Note that this method is part of the
         // KeyListener interface.
   }
   
   public void keyPressed(KeyEvent evt) { 
         // Method to respond to key presses.  Use int code = evt.getKeyCode() to
         // get a code number for the key pressed.  The value of this code
         // is given by constants in the KeyEvent class such as KeyEvent.VK_LEFT
         // for the left arrow key and KeyEvent.VK_X for the "X" key.  Override this
         // method if you want to respond when the user presses special keys like 
         // the arrow keys.  Note that this routine is part of the KeyListener
         // interface
   }
   
   public void keyReleased(KeyEvent evt) { 
         // Method to respond when the user releases a key. Use evt.getKeyCode()
         // to get the code number of the key that was released.  Override this
         // method if you want to respond when the user releases a key.  This method
         // is part of the KeyListener interface.
   }
   
   public int getFrameNumber() {
         // Get the current frame number.  The frame number will be incremented
         // each time a new frame is to be drawn.  The first frame number is 0.
         // (If frameCount is greater than zero, and if frameNumber is greater than
         // or equal to frameCount, then frameNumber returns to 0.)  For a keyboard
         // applet, you are not too likely to need frame numbers, actually.
      return frameNumber;
   }
   

   public void setFrameNumber(int frameNumber) {
           // Set the current frame number.  This is the value returned by getFrameNumber().
      if (frameNumber < 0)
         this.frameNumber = 0;
      else
         this.frameNumber = frameNumber;      
   }
   

   public long getElapsedTime() {
           // return the total number of milliseconds that the animation has been
           // running (not including the time when the applet is suspended by
           // the system or when the applet does not have the keyboard focus).
      return elapsedTime;
   }
   

   public void setFrameCount(int max) {
           // If you want your animation to loop through a set of frames over
           // and over, you should call this routine to set the frameCount to 
           // the number of frames in the animation.  Frames will be numbered
           // from 0 to frameCount - 1.  If you specify a value <= 0, the
           // frameNumber will increase indefinitely without ever returning
           // to zero.  The default value of frameCount is -1, meaning that
           // by default frameNumber does NOT loop.
      if (max <= 0)
         this.frameCount = -1;
      else 
         frameCount = max;
   }
   

   public void setMillisecondsPerFrame(int time) {
           // Set the approximate number of milliseconds to be used for each frame.
           // For example, set time = 1000 if you want each frame to be displayed for
           // about a second.  The time is only approximate, and the actual display
           // time will probably be a bit longer.  The default value of 40 is
           // probably OK for a game.
      millisecondsPerFrame = time;
   }
   

   public void setMinimumSleepTime(int time) {
           // An applet must allow some time for the computer to do other tasks.
           // In order to do this, the animation applet "sleeps" between frames.
           // This routine sets a minimum sleep time that will be applied even if
           // that will increase the display time for a frame above the value
           // specified in setMillisecondsPerFrame().  The parameter is given in
           // milliseconds.  The default value is 10.  You can set this to
           // any positive value.
      if (time <= 0)
         minimumSleepTime = 1;
      else
         minimumSleepTime = time;
   }
   
   public void setFocusBorderColor(Color c) {
            // Set the color of the three-pixel border that surrounds the applet
            // when the applet has the keyboard focus. The default color is cyan.
      focusBorderColor = c;
   }
   

   // This rest of this file is private stuff that you don't have to know about
   // when you write your own animations.

   private int frameNumber = 0;
   private int frameCount = -1;
   private int millisecondsPerFrame = 40;
   private int minimumSleepTime = 10;
   
   private long startTime;
   private long oldElapsedTime;
   private long elapsedTime;
   
   private Thread runner;
   
   private Image OSC;
   private Graphics OSG;
   
   private final static int GO = 0, SUSPEND = 1, TERMINATE = 2;
   private int status = SUSPEND;
   
   private int width = -1;
   private int height = -1;
   
   private boolean focussed = false;  // set to true when the applet has the keyboard focus
   
   Color focusBorderColor = Color.cyan;
   
   public void init() {
      setBackground(Color.gray); // Color used for border when applet doesn't have focus.
      setForeground(Color.red);
      setFont(new Font("SanSerif",Font.BOLD,14));
      addFocusListener(this);
      addKeyListener(this);
      addMouseListener(this);
      doInitialization(getSize().width - 6, getSize().height - 6);
   }
      

   synchronized public void start() {
           // Called by the system when the applet is first started 
           // or restarted after being stopped.  This routine creates
           // or restarts the thread that runs the animation.  Also,
           // if focussed is true, then the animation will start.  So
           // we should start the timing mechanism by setting startTime == 1.
      if (runner == null || !runner.isAlive()) {
         runner = new Thread(this);
         runner.start();
      }
      status = GO;
      if (focussed)
         startTime = -1;  // signal to run() to compute startTime
      notify();
   }
   
   synchronized public void stop() {
           // Called by the system to suspend the applet. Suspend the
           // animation thread by setting status to SUSPEND.
           // Also, update oldElapsedTime, which keeps track of the
           // total running time of the animation time between
           // calls to start() and stop().  Also, if focussed is
           // true, then the animation will change state from running
           // to paused, so we should record the elapsed time.
      if (focussed)
         oldElapsedTime += (System.currentTimeMillis() - startTime);
      status = SUSPEND;
      notify();
   }
   
   public void destroy() {
           // Called by the system when the applet is being permanently
           // destroyed.  This tells the animation thread to stop by
           // setting status to TERMINATE.
      if (runner != null && runner.isAlive()) {
         synchronized(this) {
            status = TERMINATE;
            notify();
         }
      }
   }
   
   public void update(Graphics g) {
           // Called by system when applet needs to be redrawn.
      paint(g);
   }
   
   synchronized public void paint(Graphics g) {
           // Draw the current frame on the applet drawing area.  If the 
           // applet has focus, draw a cyan border around the frame.  Otherwise,
           // draw a message telling the user to click on the applet to 
           // activate it.

      if (width != getSize().width || height != getSize().height) { // if size has changed, recreate frame
         doSetup();
         if (OSC != null)
            drawFrame(OSG,width-6,height-6);
      }

      if (OSC == null) { // if not enough memory for OSC, draw an error message
         g.setColor(getBackground());
         g.fillRect(0,0,width,height);
         g.setColor(getForeground());
         g.drawString("Sorry, out of Memory!", 10,25);
         return;
      }

      g.drawImage(OSC,3,3,this);
      
      if (focussed)                     // Draw a 3-pixel border.  If the applet has the
         g.setColor(focusBorderColor);  //   focus, draw it in focusBorderColor; otherwise,
      else                              //   draw it in the background color.
         g.setColor(getBackground());
      g.drawRect(0,0,width-1,height-1);
      g.drawRect(1,1,width-3,height-3);
      g.drawRect(2,2,width-5,height-5);

      if (!focussed) {                      // If the applet does not have the focus,
         g.setColor(getForeground());       //    print a message for the user.
         g.drawString("Click to activate",10,height-12);
      }

   } // end paint
   
   private void doSetup() {
           // creates OSC and graphics context for OSC
      width = getSize().width;
      height = getSize().height;
      OSC = null;  // free up any memory currently used by OSC before allocating new memory
      try {
         OSC = createImage(width-6,height-6);
         OSG = OSC.getGraphics();
         OSG.setColor(Color.black);
         OSG.setFont(new Font("Serif",Font.PLAIN,12));
      }
      catch (OutOfMemoryError e) {
         OSC = null;
         OSG = null;
      }
   }
   
   public void run() {
           // Runs the animation.  The animation thread executes this routine.
      long lastFrameTime = 0;
      while (true) {
         synchronized(this) {
            while (status == SUSPEND || !focussed) {
               try {
                  wait();  // animation has been suspended; wait for it to be restarted
               }
               catch (InterruptedException e) {
               }
            }
            if (status == TERMINATE) {  // exit from run() routine and terminate animation thread
               return;
            }
            if (width != getSize().width || height != getSize().height)  // check for applet size change
               doSetup();
            if (startTime == -1) {
               startTime = System.currentTimeMillis();
               elapsedTime = oldElapsedTime;
            }
            else
               elapsedTime = oldElapsedTime + (System.currentTimeMillis() - startTime);
            if (frameCount >= 0 && frameNumber >= frameCount)
               frameNumber = 0;
            if (OSC != null)
               drawFrame(OSG,width-6,height-6);   // draw current fram to OSC
            frameNumber++;
         }
         long time = System.currentTimeMillis();
         long sleepTime = (lastFrameTime + millisecondsPerFrame) - time;
         if (sleepTime < minimumSleepTime)
            sleepTime = minimumSleepTime;
         repaint();  // tell system to redraw the applet to display the new frame
         try {
            synchronized(this) {
              wait(sleepTime);
            }
         }
         catch (InterruptedException e) { }
         lastFrameTime = System.currentTimeMillis();
      }
   }
   
   
   synchronized public void focusGained(FocusEvent evt) {
         // The applet now has the input focus. Set focussed = true and repaint.
         // Also, if both (focussed && status == GO), the animation will start,
         // so we have to restart the timing utility by setting startTime = -1;
      focussed = true;
      repaint();  // redraw with cyan border
      if (status == GO)
         startTime = -1;  // signal to run() to compute startTime
      notify();
   }
   
   synchronized public void focusLost(FocusEvent evt) {
         // The applet has lostthe input focus. Set focussed = false and repaint.
         // Also, if both (focussed && status == GO) were previously true, then
         // the animation will be stopped at this point, so we should record the time.
      focussed = false;
      repaint();  // redraw without cyan border
      if (status == GO)
         oldElapsedTime += (System.currentTimeMillis() - startTime);
      notify();
   }
   
   public void mousePressed(MouseEvent evt) {
        // Request the input focus when the user clicks on
        // the applet.  (On most platforms, there is no need
        // for this.  However, Sun's own Java implementation
        // requires it.)
      requestFocus();
   }
   
   public void mouseEntered(MouseEvent evt) { }  // Required by the
   public void mouseExited(MouseEvent evt) { }   //    MouseListener
   public void mouseReleased(MouseEvent evt) { } //       interface.
   public void mouseClicked(MouseEvent evt) { }


} // end class AnimationApplet
