
/* 
   This applet scrolls the message "hello world (for absolutely the last time)!" across
   the screen.
*/

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

public class ScrollingHelloWorld extends Applet implements  Runnable {

   Thread runner;  // a thread that is responsible for cycling the message colors.
   
   private final static int GO = 0,           // Constants for use as value of status.
                            SUSPEND = 1,
                            TERMINATE = 2;
                            
   private volatile int status ;   // This variable is used for communication between
                                      // the applet and the thread.  The value is set by
                                      // the applet to tell the thread what to do.
                                      
   String message = "Hello World (for absolutely the last time)!";  // message to be scrolled

   
   int messagePosition = -1;   // Current position of the left end of the message, given as the
                               //   distance in pixels from the left edge of the applet.
                               //   In each frame, this is incremented by one character width
                               //   until the message has scrolled completely off the applet.
                               //   Then it is reset to zero.  The value of -1 indicates that
                               //   the scrolling has not yet begun.
   
   Font messageFont;   // Font used to display the message.
   
   int messageHeight;  // Data about size of message and of a single character.
   int messageWidth;
   int charWidth;
   

   /* Some variable used for double-buffering */
   
   Image OSC;  // The off-screen canvas (created and used in update()).
   
   int widthOfOSC, heightOfOSC;  // Current widht and height of OSC.  These
                                 // are checked against the size of the applet,
                                 // to detect any change in the applet's size.
                                 // If the size has changed, a new OSC is created.
   

   public void init() {
      setBackground(Color.white);
      messageFont = new Font("Monospaced", Font.BOLD, 30);
      FontMetrics fm = getFontMetrics(messageFont);
      messageWidth = fm.stringWidth(message);
      messageHeight = fm.getAscent();
      charWidth = fm.charWidth('H');
   }
   

   public void update(Graphics g) {
        // To implement double-buffering, the update method calls paint to
        // draw the contents of the applet on an off-screen canvas.  Then
        // the canvas is copied onto the screen.  This method is responsible
        // for creating the off-screen canvas.  It will make a new OSC if
        // the size of the applet changes.
      if (OSC == null || widthOfOSC != getSize().width || heightOfOSC != getSize().height) {
             // Create the OSC, or make a new one if applet size has changed.
         OSC = null;  // (If OSC already exists, this frees up the memory.)
         OSC = createImage(getSize().width, getSize().height);
         widthOfOSC = getSize().width;
         heightOfOSC = getSize().height;
      }
      Graphics OSG = OSC.getGraphics();  // Graphics context for drawing to OSC.
      OSG.setColor(getBackground());
      OSG.fillRect(0, 0, widthOfOSC, heightOfOSC);
      OSG.setColor(getForeground());
      OSG.setFont(getFont());
      paint(OSG);  // Draw applet contents to OSC.
      g.drawImage(OSC,0,0,this);  // Copy OSC to screen.
   }
                

   synchronized public void paint(Graphics g) {
         // Draw the current frame.
      if (messagePosition > 0) {  // (Otherwise, no frame has yet been computed.)
         g.setColor(Color.red);
         g.setFont(messageFont);
         g.drawString(message, getSize().width - messagePosition, getSize().height/2 + messageHeight/2);
      }
   }
      

   synchronized public void start() {
         // Called when the applet is being started or restarted.
         // Create a new thread or restart the existing thread.
      status = GO;
      if (runner == null || ! runner.isAlive()) {  // Thread doens't yet exist or has died for some reason.
         runner = new Thread(this);
         runner.start();
      }
      else
         notify();
   }
   

   synchronized public void stop() {
         // Called when the applet is about to be stopped.
         // Suspend the thread.
      status = SUSPEND;
      notify();
   }
   

   synchronized public void destroy() {
         // Called when the applet is about to be permanently destroyed;
         // Stop the thread.
      status = TERMINATE;
      notify();
   }  
   
   
   synchronized void nextFrame() {
          // Compute and display the next frame of the animation.  Called by run().
       messagePosition += charWidth;
       if (getSize().width - messagePosition + messageWidth < 0)  // message has moved off left edge
          messagePosition = 0;
       repaint();
   }       
      

   public void run() {
          // The run method scrolls the image while the value of status is GO,
          // and it ends when the value of status becomes TERMINATE.  If the
          // status is SUSPEND, the thread will pause until the notify()
          // method is called by another thread.
      while (status != TERMINATE) {
         synchronized(this) {
            while (status == SUSPEND)
               waitDelay();
         }
         if (status == GO)
            nextFrame();
         if (status == GO)
            waitDelay(250);
      }
   } // end run()
   
   
   synchronized void waitDelay(int milliseconds) {
         // Pause for the specified number of milliseconds
         // OR until the notify() method is called by some other thread.
      try {
         wait(milliseconds);
      }
      catch (InterruptedException e) {
      }
   }
      
   synchronized void waitDelay() {
         // Pause until the notify() method is called by some other thread.
      try {
         wait();
      }
      catch (InterruptedException e) {
      }
   }
      
   
} // end class ScrollingHelloWorld


