// PulseText
// Written by Eric Blood 01/09/96
//
// Note: If you use this code, please give me credit.  If you're
//       a commercial entity, give me money (or hardware, or
//       a job).  
//
// 02/09/96
//    Added the ability to set the background (with the occurrence of the
//    page blackouts).  Cleaned up the String to Color stuff.

import java.awt.*;
import java.util.*;

public class PulseText extends java.applet.Applet implements Runnable
{
   Thread pulser;

   String text;
   int    textlen;
   int    textindex;
   int    textwidths[];

   Font  font;
   int   speed;
   Color textcolors[];
   Color background;
   int   numcolors;

   Image     offscreen;
   Graphics  offgraphics;
   Dimension offscreensize;

   // init()
   //
   // Handles the argument parsing, and sets up the fonts and colors.
   
   public void init()
   {
      String temp;

      font = new Font("TimesRoman",Font.PLAIN,24);

      temp = getParameter("text");
      if (temp == null)
         text = "This is a test";
      else
         text = temp;

      textlen    = text.length();
      textindex  = 0;
      textwidths = null;

      // The "blinking" was taken from the Blinker applet example
      temp = getParameter("speed");
      if (temp == null)
         speed = 400;
      else
         speed = 1000 / Integer.valueOf(temp).intValue();
   
      temp = getParameter("tailLength");
      if (temp == null)
         numcolors = 4;
      else
         numcolors = Integer.valueOf(temp).intValue();

      if (numcolors >= textlen)
         numcolors = textlen - 1;

      Color pulse, foreground;
      temp = getParameter("pulse");
      if (temp == null)
         pulse = Color.red;
      else
         pulse = Utilities.Color(temp);
         
      temp = getParameter("foreground");
      if (temp == null)
         foreground = Color.black;
      else
         foreground = Utilities.Color(temp);

      temp = getParameter("background");
      if (temp == null)
         background = getBackground();
      else
         background = Utilities.Color(temp);

      // This next bit of code creates the color variants and stores them in
      // the array of textcolors.
      textcolors                = new Color[numcolors];
      textcolors[0]             = pulse;
      textcolors[numcolors - 1] = foreground;

      // These are the differences in the components of the pulse and foreground divided
      // over the array.
      int reddiff   = (textcolors[0].getRed() - textcolors[numcolors - 1].getRed()) / (numcolors - 1);
      int greendiff = (textcolors[0].getGreen() - textcolors[numcolors - 1].getGreen()) / (numcolors - 1);
      int bluediff  = (textcolors[0].getBlue() - textcolors[numcolors - 1].getBlue()) / (numcolors - 1);

      for (int n = 1; n < numcolors - 1; n++)
         textcolors[n] = new Color(textcolors[0].getRed() - (reddiff * n),
                                   textcolors[0].getGreen() - (greendiff * n),
                                   textcolors[0].getBlue() - (bluediff * n));
   }    
    
   // update()
   //
   // In the great order of things, update() is called before paint() (see
   // the source in java.awt.Component) and it will clear the space.  This
   // isn't needed because PulseText redraws the whole thing anyways.  So,
   // by overriding update() instead of paint, I save some time.
   //
   // The flicker is avoided by drawing into an image and then drawing the
   // resulting image.  Trust me it works.  

   public void update(Graphics g)
   {
      int x, y;
      Dimension d = size();
      boolean drawtext = false;

      // Here, if the offscreen buffer doesn't exist or the size has changed,
      // then create a new one, and clear it to the color of the set background.
      if ((offscreen == null) || (d.width != offscreensize.width) || (d.height != offscreensize.height)) {
         offscreen = createImage(d.width, d.height);
         offscreensize = d;
         offgraphics = offscreen.getGraphics();
         offgraphics.setFont(font);
      	offgraphics.setColor(background);
      	offgraphics.fillRect(0, 0, offscreensize.width, offscreensize.height);

         drawtext = true;
      }

      // The font metrics are used to get the starting coordinates of where to
      // pain the text.
      FontMetrics fm = offgraphics.getFontMetrics();
      x = (d.width - fm.stringWidth(text)) / 2;
      y = ((d.height - fm.getHeight()) / 2) + fm.getMaxAscent();

      // If the image has been created, than draw the text for the
      // first time in the default foreground color.
      if (drawtext) {
         offgraphics.setColor(textcolors[numcolors - 1]);
         offgraphics.drawString(text, x, y);
      }

      // If the textwidths array hasn't been created, then create it.
      // It is used to hold the width of each character in the text
      // string.  This way when a single character is drawn, it's drawn
      // in the correct location.
      if (textwidths == null) {
         textwidths = new int[textlen];
         textwidths[0] = x;
         for (int i = 1; i < textlen; i++)
            textwidths[i] = textwidths[i - 1] + fm.charWidth(text.charAt(i - 1));
      }

      // Here, only the character in string that need their color updated
      // get updated.  The textindex holds the location of the head of the
      // pulse "tail".  From the end of the "tail" to the head, each
      // character is updated with the next color in the area.  
      int j;
      char t;
      for (int i = 0; i < numcolors; i++) {
         j = (textindex - i + textlen) % textlen;
         t = text.charAt(j);

         if (t != ' ') {
            offgraphics.setColor(textcolors[i]);
            offgraphics.drawString(text.substring(j, j + 1), textwidths[j], y);
         }
      }

      // The image is drawn to the graphics context of the screen.
      g.drawImage(offscreen, 0, 0, null);
      // The "tail" index is bumped one.
      textindex = (textindex + 1) % textlen;
   }

   // When ever the viewer says "jump..."
   
   public void start()
   {
      pulser = new Thread(this);
      pulser.start();
   }

   public void stop()
   {
      pulser.stop();
   }

   // run()
   //
   // The heart of PulseText.  PulseText is a thread, so while it is
   // running sleep for a bit, and then repaint().  Doing this
   // forever (or until we get stop()ed).
   public void run()
   {
      while(true) {
         try {
			   Thread.currentThread().sleep(speed);
   	   } catch (InterruptedException e) {
   
         }	
		   repaint();
      }
   }
   
}

class Utilities {

   // Color()
   //
   // This will read a string of form "000,000,000" and convert
   // it to a color value.
   
   public static Color Color(String color) {
      StringTokenizer st = new StringTokenizer(color, ",\t\r\n ");
      int c[] = new int[3];
      
      for (int n = 0; st.hasMoreTokens() && n < 3; n++)
         c[n] = Integer.valueOf(st.nextToken()).intValue();

      return new Color(c[0], c[1], c[2]);
   }
   
}
