import java.util.Vector;
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
public class TextScroll extends Applet implements Runnable {

  public static final String VERSION = "2.8.3";
  public static final int MAX_SPEED = 100;
  public static final int MIN_SPEED = 1;
  public static final int DEFAULT_SPEED = 70;
  public static final int BG_LOAD_COLOR = (224 << 16) | (224 << 8) | 255;
  public static final int FG_LOAD_COLOR = (32 << 16) | (32 << 8) | 144;
  public static final int BG_ERROR_COLOR = (255 << 16) | (224 << 8) | 224;
  public static final int FG_ERROR_COLOR = (144 << 16) | (32 << 8) | 32;
  public static final int T_READY = 1;
  public static final int T_LOADING = 2;
  public static final int T_FORMATTING = 4;
  public static final int T_NO_DATA_SPECIFIED = 8;
  public static final int T_LOAD_ERROR = 16;
  public static final String STATUS_MSG = "TextScroll -- Version " + TextScroll.VERSION;
  public static final String DEFAULT_FONT_FACE = "SansSerif";
  public static final int DEFAULT_FONT_SIZE = 12;
  public static final int DEFAULT_LEFT_WIDTH = 0;
  private Image buffImage;
  private Graphics buffGraphics;
  private int buffHeight, buffWidth;
  private Color leftBackgroundColor,
                leftForegroundColor,
                rightBackgroundColor,
                rightForegroundColor,
                bgLoadColor,
                fgLoadColor;
  private Font initialFont;
  private Font leftFont;
  private Font rightFont;
  private int leftFontSize;
  private int rightFontSize;
  private int fontHeight;
  private String fileName;
  private URL dataFileURL;
  private int width, height;
  private int leftWidth = TextScroll.DEFAULT_LEFT_WIDTH;
  private boolean leftCenter = false;
  private boolean rightCenter = false;
  private int speed;
  private URL targetURL = null;
  private boolean shouldWrapText = true;
  private String targetFrame = null;
  private int inset = 3;
  private boolean running;
  private boolean showLeftText = false;
  private String leftText = "";
  private int lineSpacing = 5;
  private String[] text;
  private int frame = 16;
  private int currLine = 0;
  private int iteration = 0;
  private int refreshValue = 1;
  private Thread scroller;
  private int offset = 1;
  private boolean mouseInside = false;
  private int status;
  private DataLoader loader = null;
  private boolean reloaded = true;
  private DirectiveManager directiveManager = null;
  public void init () {
    this.status = TextScroll.T_LOADING;
    this.text = null;
    this.directiveManager = new DirectiveManager (this);
    System.err.println ("TextScroll  v" + TextScroll.VERSION +"\nCopyright (C) 1998   Kevin Swan, 013639s@dragon.acadiau.ca");
    running = false;
    String param = null;
    this.width  = this.getSize ().width;
    this.height = this.getSize ().height;
    this.buffWidth = this.width;
    this.buffHeight = this.height + 200;

    /*
     * Get the width of the left text area.
     */
    param = getParameter ("leftwidth");
    if (param == null)
      this.leftWidth = TextScroll.DEFAULT_LEFT_WIDTH;
    else
      try {
        this.leftWidth = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.leftWidth = TextScroll.DEFAULT_LEFT_WIDTH;
      }

    /*
     * Get the color parameters for the text areas.
     */
    param = getParameter ("leftforeground");
    if (param == null)
      leftForegroundColor = Color.black;
    else
      leftForegroundColor = getColorFromString (param);
    if (leftForegroundColor == null)
      leftForegroundColor = Color.black;

    param = getParameter ("leftbackground");
    if (param == null)
      leftBackgroundColor = Color.white;
    else
      leftBackgroundColor = getColorFromString (param);
    if (leftBackgroundColor == null)
      leftBackgroundColor = Color.white;

    param = getParameter ("rightforeground");
    if (param == null)
      rightForegroundColor = Color.black;
    else
      rightForegroundColor = getColorFromString (param);
    if (rightForegroundColor == null)
      rightForegroundColor = Color.black;

    param = getParameter ("rightbackground");
    if (param == null)
      rightBackgroundColor = Color.white;
    else
      rightBackgroundColor = getColorFromString (param);
    if (rightBackgroundColor == null)
      rightBackgroundColor = Color.white;

    param = getParameter ("foreground");
    if (param == null)
      rightForegroundColor = Color.black;
    else
      rightForegroundColor = getColorFromString (param);
    if (rightForegroundColor == null)
      rightForegroundColor = Color.black;

    param = getParameter ("background");
    if (param == null)
      rightBackgroundColor = Color.white;
    else
      rightBackgroundColor = getColorFromString (param);
    if (rightBackgroundColor == null)
      rightBackgroundColor = Color.white;

    param = getParameter ("fgloadcolor");
    if (param == null)
      fgLoadColor = new Color (TextScroll.FG_LOAD_COLOR);
    else
      fgLoadColor = getColorFromString (param);
    if (fgLoadColor == null)
      fgLoadColor = new Color (TextScroll.FG_LOAD_COLOR);

    param = getParameter ("bgloadcolor");
    if (param == null)
      bgLoadColor = new Color (TextScroll.BG_LOAD_COLOR);
    else
      bgLoadColor = getColorFromString (param);
    if (bgLoadColor == null)
      bgLoadColor = new Color (TextScroll.BG_LOAD_COLOR);


    /*
     * Get the desired Font information.
     */
    param = getParameter ("leftfontsize");
    if (param == null)
      this.leftFontSize = TextScroll.DEFAULT_FONT_SIZE;
    else
      try {
        this.leftFontSize = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.leftFontSize = TextScroll.DEFAULT_FONT_SIZE;
      }

    param = getParameter ("rightfontsize");
    if (param == null)
      this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE;
    else
      try {
        this.rightFontSize = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE;
      }

    param = getParameter ("fontsize");
    if (param == null)
      this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE;
    else
      try {
        this.rightFontSize = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE;
      }

    String face = null;

    param = getParameter ("leftfontface");
    if (param == null)
      face = TextScroll.DEFAULT_FONT_FACE;
    else
      face = param;

    this.leftFont = new Font (face, Font.PLAIN, this.leftFontSize);

    param = getParameter ("rightfontface");
    if (param == null)
      face = TextScroll.DEFAULT_FONT_FACE;
    else
      face = param;

    this.rightFont = new Font (face, Font.PLAIN, this.rightFontSize);
    this.initialFont = new Font (face, Font.PLAIN, this.rightFontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont);
    this.fontHeight =
          fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading ();
    frame = this.lineSpacing + this.fontHeight;

    param = getParameter ("fontface");
    if (param == null)
      face = TextScroll.DEFAULT_FONT_FACE;
    else
      face = param;

    this.rightFont = new Font (face, Font.PLAIN, this.rightFontSize);
    this.initialFont = new Font (face, Font.PLAIN, this.rightFontSize);
    fm = this.getToolkit ().getFontMetrics (this.rightFont);
    this.fontHeight =
          fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading ();
    frame = this.lineSpacing + this.fontHeight;

    /*
     * Set the name of the file to load.
     */
    param = getParameter ("data");
    this.dataFileURL = null;
    if (param == null)
      this.status = TextScroll.T_NO_DATA_SPECIFIED;
    else {
      try {
        if (!param.startsWith ("http://"))
          this.dataFileURL = new URL (getDocumentBase (), param);
        else
          this.dataFileURL = new URL (param);
      } catch (MalformedURLException mue) {
        this.status = TextScroll.T_LOAD_ERROR;
      }
    }

    /*
     * Check if the user wants line wrapping or not.
     */
    param = getParameter ("wraptext");
    if (param != null)
      this.shouldWrapText = (new Boolean (param)).booleanValue ();

    /* Look for a different offset. */
    param = getParameter ("offset");
    if (param == null)
      this.offset = 1;
    else
      try {
        this.offset = Integer.parseInt (param);
      } catch (NumberFormatException  nfe) {
        this.offset = 1;
      }

    /* Set the speed information. */
    param = getParameter ("speed");
    if (param == null)
      this.speed = TextScroll.DEFAULT_SPEED;
    else
      try {
        this.speed = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.speed = TextScroll.DEFAULT_SPEED;
      }

    if (this.speed > TextScroll.MAX_SPEED ||
        this.speed < TextScroll.MIN_SPEED)
      this.speed = TextScroll.DEFAULT_SPEED;

    /* Set the refresh information. */
    param = getParameter ("refresh");
    if (param == null)
      this.refreshValue = -1;
    else
      try {
        this.refreshValue = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.refreshValue = -1;
      }

    this.resize (this.width, this.height);
    this.buffImage = this.createImage (this.buffWidth, this.buffHeight);
    this.buffGraphics = this.buffImage.getGraphics ();

    /* Prepare the offscreen buffer. */

    this.buffGraphics.setColor (this.leftBackgroundColor);
    this.buffGraphics.fillRect (0, 0, this.leftWidth, this.buffHeight);

    this.buffGraphics.setColor (this.rightBackgroundColor);
    this.buffGraphics.fillRect (
               this.leftWidth, 0, this.buffWidth, this.buffHeight);
    loadData (this.dataFileURL);
  }
  private Graphics getBuffGraphics () {
    if (this.buffGraphics == null) {
      this.buffImage = this.createImage (this.buffWidth, this.buffHeight);
      this.buffGraphics = this.buffImage.getGraphics ();
    }
    return this.buffGraphics;
  }
  public static Color getColorFromString (String rgb) {
    int red, green, blue;
    red = green = blue = 0;
    if (rgb == null)
      return null;
    try{
      red   = Integer.parseInt ((rgb.substring (0, rgb.indexOf (","))).trim ());
      green = Integer.parseInt ((rgb.substring(rgb.indexOf (",") + 1, rgb.lastIndexOf (","))).trim ());
      blue  = Integer.parseInt ((rgb.substring(rgb.lastIndexOf (",") + 1)).trim ());
    } catch (NumberFormatException nfe) {
      return null;
    }
    try {
      return new Color(red, green, blue);
    } catch (IllegalArgumentException iae) {
      return null;
    }
  } 
  public void run () {
    while (!this.isReady ()) {
      if (this.getStatus () > TextScroll.T_LOADING) {
        repaint ();
        return;
      }
      try {
        scroller.sleep (200);
      } catch (InterruptedException ie) {
        return;
      }
    }
    while (running) {
      this.getBuffGraphics ().copyArea (
                  0,
                  offset,
                  this.buffWidth,
                  this.height + this.fontHeight + this.lineSpacing,
                  0, 0 - offset);
      frame -= offset;
      if (frame < 0) {
        String line = this.text [this.currLine];
        this.currLine++;

        /*
         * If we've hit the end of the presentation, reset the line
         * variable, increment the iteration variable, and test if
         * we should refresh our data.
         */
        if (this.currLine == this.text.length) {
          this.currLine = 0;
          this.iteration++;

          /* See if we should refresh our data. */
          if ((this.refreshValue > 0) &&
              (this.iteration >= this.refreshValue)) {
            /* 
             * First, we should check to see if we've already tried to refresh
             * the data.  If we have, and its ready now, replace the data.
             */
            if (this.reloaded) {
              if (this.isReady ()) {
                String[] tmpArr = this.loader.getData ();
                this.text = new String [tmpArr.length];
                System.arraycopy (tmpArr, 0, this.text, 0, tmpArr.length);
                this.iteration = 0;
                this.reloaded = false;
              }
            } else
              /* If we haven't started refreshing it yet, do it now. */
              this.refreshData ();
          }
        }

        /*
         * If the line is a method directive, try to service it.
         */
        if (line.startsWith ("^^"))
          if (!this.directiveManager.performDirective (line))
            System.err.println ("Illegal directive call:\n\t" + line);
          else
            continue;

        /* Treat the line as normal. */

        /**
         * If we have a left message set, and we haven't
         * displayed it yet, now is the time to display it.
         */

        FontMetrics fm = this.getToolkit ().getFontMetrics (this.leftFont);
        if (this.showLeftText) {
          this.getBuffGraphics ().setColor (this.leftForegroundColor);
          this.getBuffGraphics ().setFont (this.leftFont);
          if (!this.leftCenter) {
            this.getBuffGraphics ().drawString (
                    leftText,
                    0,
                    this.height + this.lineSpacing + fm.getMaxAscent ());
          } else {
            /*
             * We must center the text.  We will do this in 3 steps:
             *   1. Find the length of the text.
             *   2. Calculate what x value to start drawing the text at.
             *   3. Draw the text.
             */
            int length = fm.stringWidth (leftText);
            int start = (this.leftWidth / 2) - (length / 2);
            start = (start < 0) ? 0 : start;
            this.getBuffGraphics ().drawString (
                    leftText,
                    start,
                    this.height + this.lineSpacing + fm.getMaxAscent ());
          }

          this.getBuffGraphics ().setColor (this.rightForegroundColor);
          this.showLeftText = false;
        }

        this.getBuffGraphics ().setColor (this.rightForegroundColor);
        this.getBuffGraphics ().setFont (this.rightFont);
        fm = this.getToolkit ().getFontMetrics (this.rightFont);
        frame = this.lineSpacing + this.fontHeight;
        if (!this.rightCenter) {
          this.getBuffGraphics ().drawString (
                  line,
                  this.leftWidth + this.inset,
                  this.height + this.lineSpacing + fm.getMaxAscent ());
        } else {
          /*
           * We must center the text.  We will do this in 3 steps:
           *   1. Find the length of the text.
           *   2. Calculate what x value to start drawing the text at.
           *   3. Draw the text.
           */
          int length = fm.stringWidth (line);
          int start = ((this.width - this.leftWidth) / 2) - (length / 2);
          start = (start < this.leftWidth) ? this.leftWidth : start;
          this.getBuffGraphics ().drawString (
                  line,
                  start + this.leftWidth,
                  this.height + this.lineSpacing + fm.getMaxAscent ());
        }
      }

      try {
        scroller.sleep (TextScroll.MAX_SPEED + 1 - this.speed);
      } catch (InterruptedException ie) {
        return;
      }

      this.repaint ();

    }

    return;

  } /* run () */



  /**
   * This method reloads the text data file from the
   * server.  Note that by the time this method is
   * called, it will probably take some time to
   * completely load, and we can't really replace the
   * whole data array in the middle of a display.  We'll
   * simply refresh the <CODE>DataLoader</CODE>.  The
   * next time we get to the end of the presentation,
   * we'll check if the data is ready.  But, we do have
   * to note somehow that we tried to refresh it.  We'll
   * set a flag.
   */
  private void refreshData () {
    this.loader.refresh ();
    this.reloaded = true;
  }



  /**
   * This method loads data from a <CODE>URL</CODE> into
   * a <CODE>DataLoader</CODE>.  The applet should
   * periodically check its <CODE>DataLoader</CODE> to see
   * if the data is ready.  It is given the URL of the text
   * file to read.
   *
   * @param url The URL of the text data file to load.
   */
  private void loadData (URL url) {
    /* If it is null, just ignore it. */
    if (url == null)
      return;
    else
      this.loader = new DataLoader (
                      url,
                      this.initialFont,
                      this.width - this.leftWidth,
                      this.shouldWrapText);
    return;
  }



  /**
   * Called to paint the screen.
   */
  public void paint (Graphics g) {
    if (this.status > TextScroll.T_READY)
      this.displayStatus (g);
    else
      g.drawImage (this.buffImage, 0, 0, this);
  }



  /**
   * Called to update the screen.
   */
  public void update (Graphics g) {
    if (this.status > TextScroll.T_READY)
      this.displayStatus (g);
    else
      g.drawImage (this.buffImage, 0, 0, this);
  } 



  /**
   * Called to print the appropriate status information if the applet
   * is not able to begin scrolling yet.
   *
   * @param g The <CODE>Graphics</CODE> object to draw on.
   */
  private void displayStatus (Graphics g) {
    if (this.status == TextScroll.T_READY)
      return;

    g.setFont (new Font ("SansSerif", Font.PLAIN, 12));
    switch (this.getStatus ()) {
      case (TextScroll.T_LOADING):
                g.setColor (this.bgLoadColor);
                g.fillRect (0, 0, this.width, this.height);
                g.setColor (this.fgLoadColor);
                g.drawString ("Loading data ...", 5, 15);
                return;
      case (TextScroll.T_FORMATTING):
                g.setColor (this.bgLoadColor);
                g.fillRect (0, 0, this.width, this.height);
                g.setColor (this.fgLoadColor);
                g.drawString ("Formatting data ...", 5, 15);
                return;
      case (TextScroll.T_NO_DATA_SPECIFIED):
                g.setColor (new Color (TextScroll.BG_ERROR_COLOR));
                g.fillRect (0, 0, this.width, this.height);
                g.setColor (new Color (TextScroll.FG_ERROR_COLOR));
                g.drawString ("No \"data\" parameter specified.", 5, 15);
                return;
      case (TextScroll.T_LOAD_ERROR):
                g.setColor (new Color (TextScroll.BG_ERROR_COLOR));
                g.fillRect (0, 0, this.width, this.height);
                g.setColor (new Color (TextScroll.FG_ERROR_COLOR));
                g.drawString ("Couldn't read specified text file.", 5, 15);
                return;
    }
  } /* displayStatus () */



  /**
   * Called to start this applet.  If the applet has been running
   * before, we will pick up where we left off.
   */
  public void start () {
    running = true;
    (scroller = new Thread (this)).start ();
    if (this.loader != null)
      this.loader.start ();
    else
      System.err.println ("ERROR: DataLoader reference was lost.");
  }



  /**
   * Stop the applet.
   */
  public void stop () {
    running = false;
    if (this.scroller != null)
      this.scroller.stop ();
    if (this.loader != null)
      this.loader.stop ();
  }


  /**
   * If the Applet is running, stop it.  If it is stopped, restart it.
   */
  private void toggle () {
    if (this.running)
      this.stop ();
    else
      this.start ();
  }



  /**
   * Method called when the mouse pointer enters the applet.
   *
   * @param e The actual event
   * @param x The x coordinate the event occurred at.
   * @param y The y coordinate the event occurred at.
   *
   * @return true if the event is handled, false otherwise.
   */
  public boolean mouseEnter (Event e, int x, int y) {
    this.mouseInside = true;
    if (this.getURL () == null)
      this.showStatus (TextScroll.STATUS_MSG);
    else
      this.showStatus (this.getURL ().toString ());
    return true;
  }


  /**
   * Method called when the mouse pointer leaves the applet.
   *
   * @param e The actual event
   * @param x The x coordinate the event occurred at.
   * @param y The y coordinate the event occurred at.
   *
   * @return true if the event is handled, false otherwise.
   */
  public boolean mouseExit (Event e, int x, int y) {
    this.mouseInside = false;
    this.showStatus ("");
    return true;
  }


  /**
   * Method called when the user clicks.
   *
   * @param e The actual event.
   * @param x The x coordinate the even occurred at.
   * @param y The y coordinate the even occurred at.
   *
   * @return true if the event is handled, false otherwise.
   */
  public boolean mouseDown (Event me, int x, int y) {
    if (this.getURL () == null)
      this.toggle ();
    else
      try {
        if (this.getAppletContext () != null) {
          if (this.targetFrame == null)
            this.getAppletContext ().showDocument (this.getURL ());
          else
            this.getAppletContext ().showDocument (this.getURL (),
                                                   this.targetFrame);
        }
      } catch (Exception e) {
        this.toggle ();
      }
    return true;
  }



  /**
   * This method is used to determine whether the data is ready or not.
   *
   * @return An <CODE>int</CODE> value which is one of the constants:
   *           <UL>
   *             <LI><CODE>T_READY</CODE> - If the text is ready.
   *             <LI><CODE>T_LOADING</CODE> - If the text is loading.
   *             <LI><CODE>T_NO_DATA_SPECIFIED</CODE> - If the text could
   *                 not be loaded because no &quot;data&quot; value was
   *                 specified in a <CODE>&lt;PARAM&gt;</CODE> tag.
   *             <LI><CODE>T_LOAD_ERROR</CODE> - If an error occurred
   *                 during loading of the text data, probably a file
   *                 not found or a permissions problem.
   *           </UL>
   */
  public int getStatus () {
    return this.status;
  }



  /**
   * A method used to determine whether we're ready to start the animation
   * or not.
   *
   * @return <CODE>true</CODE> if all the necessary data has been loaded,
   *         <CODE>false</CODE> otherwise.
   */
  public boolean isReady () {
    if (this.text == null) {
      if (this.loader.dataReady ()) {
        String[] tmpArr = this.loader.getData ();
        this.text = new String [tmpArr.length];
        System.arraycopy (tmpArr, 0, this.text, 0, tmpArr.length);
        this.status = TextScroll.T_READY;
      } else {
        if (this.loader.errorOccurred ())
          this.status = TextScroll.T_LOAD_ERROR;
        else
          this.status = TextScroll.T_LOADING;
      }
    }

    return ((this.status & TextScroll.T_READY) == TextScroll.T_READY);
  }



  /**
   * This method is used to specify whether the text in the
   * left pane should be centered or not.  The String argument
   * must either be &quot;true&quot; or &quot;false&quot;  The
   * comparison is case-insensitive.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text in the left pane should
   *                be centered, or &quot;false&quot; to specify
   *                normal formatting (left align).  Any other
   *                values are ignored, and a quiet message is
   *                printed to stderr.
   */
  public void setLeftCenter (String boolStr) {
    if (boolStr.equalsIgnoreCase ("true"))
      this.leftCenter = true;
    else if (boolStr.equalsIgnoreCase ("false"))
      this.leftCenter = false;
    else
      System.err.println (
           "setLeftCenter directive ignored - invalid argument: " +
           boolStr);
  }



  /**
   * This method is used to specify whether the text in the
   * right pane should be centered or not.  The String argument
   * must either be &quot;true&quot; or &quot;false&quot;  The
   * comparison is case-insensitive.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text in the right pane should
   *                be centered, or &quot;false&quot; to specify
   *                normal formatting (left align).  Any other
   *                values are ignored, and a quiet message is
   *                printed to stderr.
   */
  public void setRightCenter (String boolStr) {
    if (boolStr.equalsIgnoreCase ("true"))
      this.rightCenter = true;
    else if (boolStr.equalsIgnoreCase ("false"))
      this.rightCenter = false;
    else
      System.err.println (
           "setRightCenter directive ignored - invalid argument: " +
           boolStr);
  }



  /**
   * This method is used to specify whether the text in the
   * right pane should be centered or not.  The String argument
   * must either be &quot;true&quot; or &quot;false&quot;  The
   * comparison is case-insensitive.  This method is left in
   * for compatibility reasons.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text in the right pane should
   *                be centered, or &quot;false&quot; to specify
   *                normal formatting (left align).  Any other
   *                values are ignored, and a quiet message is
   *                printed to stderr.
   */
  public void setCenter (String boolStr) {
    if (boolStr.equalsIgnoreCase ("true"))
      this.rightCenter = true;
    else if (boolStr.equalsIgnoreCase ("false"))
      this.rightCenter = false;
    else
      System.err.println (
           "setCenter directive ignored - invalid argument: " +
           boolStr);
  }



  /**
   * This method is used to turn bold font styling for the
   * left pane on or off.  It expects a single String argument
   * which must be either &quot;true&quot; or &quot;false&quot;.
   * The comparison is case-insensitive.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text displayed in the left
   *                pane should be bold, or &quot;false&quot; to
   *                specify non-bold styling.  Any other values are
   *                ignored, and a quiet message is printed to stderr.
   */
  public void setLeftBold (String boolStr) {
    int style = this.leftFont.getStyle ();
    if (boolStr.equalsIgnoreCase ("true"))
      style = style | Font.BOLD;
    else if (boolStr.equalsIgnoreCase ("false"))
      style = style & ~Font.BOLD;
    else {
      System.err.println ("setLeftBold directive ignored - invalid argument: " +
               boolStr);
      return;
    }
    this.leftFont =
           new Font (this.leftFont.getName (), style, this.leftFontSize);
  }



  /**
   * This method is used to turn bold font styling for the
   * right pane on or off.  It expects a single String argument
   * which must be either &quot;true&quot; or &quot;false&quot;.
   * The comparison is case-insensitive.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text displayed in the right
   *                pane should be bold, or &quot;false&quot; to
   *                specify non-bold styling.  Any other values are
   *                ignored, and a quiet message is printed to stderr.
   */
  public void setRightBold (String boolStr) {
    int style = this.rightFont.getStyle ();
    if (boolStr.equalsIgnoreCase ("true"))
      style = style | Font.BOLD;
    else if (boolStr.equalsIgnoreCase ("false"))
      style = style & ~Font.BOLD;
    else {
      System.err.println (
          "Error: Invalid int arg for setBold or setRightBold: " +
          boolStr);
      return;
    }
    this.rightFont =
        new Font (this.rightFont.getName (), style, this.rightFontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont);
    this.fontHeight =
        fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading ();
    this.getBuffGraphics ().setFont (this.rightFont);
  }



  /**
   * This method is used to turn bold font styling for the
   * right pane on or off.  It expects a single String argument
   * which must be either &quot;true&quot; or &quot;false&quot;.
   * The comparison is case-insensitive.  This method is left
   * in for compatibility purposes.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text displayed in the right
   *                pane should be bold, or &quot;false&quot; to
   *                specify non-bold styling.  Any other values are
   *                ignored, and a quiet message is printed to stderr.
   */
  public void setBold (String boolStr) {
    this.setRightBold (boolStr);
  }



  /**
   * This method is used to turn italic font styling for the
   * left pane on or off.  It expects a single String argument
   * which must be either &quot;true&quot; or &quot;false&quot;.
   * The comparison is case-insensitive.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text displayed in the left
   *                pane should be italic, or &quot;false&quot; to
   *                specify non-italic styling.  Any other values
   *                are ignored, and a quiet message is printed to
   *                stderr.
   */
  public void setLeftItalic (String boolStr) {
    int style = this.leftFont.getStyle ();
    if (boolStr.equalsIgnoreCase ("true"))
      style = style | Font.ITALIC;
    else if (boolStr.equalsIgnoreCase ("false"))
      style = style & ~Font.ITALIC;
    else {
      System.err.println (
          "Error: Invalid int arg for setLeftItalic: " +
          boolStr);
      return;
    }
    this.leftFont =
            new Font (this.leftFont.getName (), style, this.leftFontSize);
  }



  /**
   * This method is used to turn italic font styling for the
   * right pane on or off.  It expects a single String argument
   * which must be either &quot;true&quot; or &quot;false&quot;.
   * The comparison is case-insensitive.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text displayed in the right
   *                pane should be italic, or &quot;false&quot; to
   *                specify non-italic styling.  Any other values
   *                are ignored, and a quiet message is printed to
   *                stderr.
   */
  public void setRightItalic (String boolStr) {
    int style = this.rightFont.getStyle ();
    if (boolStr.equalsIgnoreCase ("true"))
      style = style | Font.ITALIC;
    else if (boolStr.equalsIgnoreCase ("false"))
      style = style & ~Font.ITALIC;
    else {
      System.err.println (
          "Error: Invalid int arg for setItalic or setRightItalic: " +
          boolStr);
      return;
    }
    this.rightFont =
            new Font (this.rightFont.getName (), style, this.rightFontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont);
    this.fontHeight =
          fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading ();
    this.getBuffGraphics ().setFont (this.rightFont);
  }



  /**
   * This method is used to turn italic font styling for the
   * right pane on or off.  It expects a single String argument
   * which must be either &quot;true&quot; or &quot;false&quot;.
   * The comparison is case-insensitive.  This method is left
   * in for compatibility purposes.
   *
   * @param boolStr A String.  Must be &quot;true&quot; to specify
   *                that all further text displayed in the right
   *                pane should be italic, or &quot;false&quot; to
   *                specify non-italic styling.  Any other values
   *                are ignored, and a quiet message is printed to
   *                stderr.
   */
  public void setItalic (String boolStr) {
    this.setRightItalic (boolStr);
  }




  /**
   * This method is used to set the inset value.  The inset is the
   * x coordinate where the text will be drawn at.  The default is 3.
   * The argument must be a String object which can be parsed to an
   * int.
   *
   * @param insetStr A String which can be parsed to an int.
   */
  public void setInset (String insetStr) {
    int val;

    try {
      val = Integer.parseInt (insetStr);
    } catch (NumberFormatException nfe) {
      System.err.println ("setInset directive ignored - invalid argument: " +
            insetStr + " (Expected a number)");
      return;
    }

    this.inset = val;

  }



  /**
   * This method sets the currently active URL to the given URL,
   * such that if the user clicks anywhere in the applet area
   * after this method has been invoked, it will load the named
   * URL, unless <CODE>URLString</CODE> is &quot;null&quot;
   * (case insensitive), in which case clicking will simply
   * result in toggling scrolling.
   *
   * <P>Note that in versions 2.7 and greater, the user can specify
   * a target frame for the URL to be displayed in as well.  The
   * frame name must be specified, followed by a comma, followed
   * by the target URL.
   *
   * <P>This method was modified in version 2.8.2 to be more
   * intelligent in determining if the user really wants a frame
   * or not.  Some URLs contain commas.  This method now only
   * treats it as a frame if there is a comma before the first
   * &quot;http://&quot; string.
   *
   * @param URLString The URL to load.  If the string is only a URL,
   *                  then the page will be loaded in the current
   *                  frame.  If it consists of a string, followed
   *                  by a comma, followed by another string, the
   *                  first string will be treated as the target
   *                  frame name, and the second will be treated
   *                  as the target URL.  If <CODE>URLString</CODE>
   *                  is &quot;null.&quot; (case insensitive),
   *                  then the target URL and the target frame are
   *                  both set to <CODE>null</CODE>.
   */
  public void setURL (String URLString) {

    /*
     * They might want a target frame, see if they've specified one.
     */
    if (URLString == null) {
        this.setURL ((URL) null);
        this.setTargetFrame (null);
    }

    /*
     * If the URL string contains a comma, and that comma appears
     * before the http://, then we have a target frame.  Otherwise,
     * just treat the whole string as a single URL.
     */
    if (URLString.indexOf (',') >= 0)
      if (URLString.indexOf (',') < URLString.indexOf ("http://")) {
        String targetFrame = URLString.substring (0, URLString.indexOf (','));
        String targetURL   = URLString.substring (URLString.indexOf (',') + 1,
                                                  URLString.length ());
        this.setURL (targetURL);
        this.setTargetFrame (targetFrame);
        return;
      }

    this.setTargetFrame (null);

    if (URLString.equalsIgnoreCase ("null")) {
      this.setURL ((URL) null);
      this.setTargetFrame (null);
    } else
      try {
        this.setURL (new URL (URLString));
      } catch (MalformedURLException mue) {
        System.err.println ("setURL directive ignored - invalid argument: " +
              URLString + "\n(Unsetting clickable link)");
        this.setURL ((URL) null);
      }
  }



  /**
   * This method sets the name of the target frame to display the
   * target URL in.  If <CODE>targetFrame</CODE> is null, then the
   * target URLs will be displayed in the current browser frame.
   *
   * @param targetFrame A <CODE>String</CODE> name of the target
   *                    frame to display the contents of the target
   *                    URL in when the applet is clicked on.
   */
  public void setTargetFrame (String targetFrame) {
    if (targetFrame != null) {
      targetFrame = targetFrame.trim ();
      if (targetFrame.equalsIgnoreCase ("null"))
        targetFrame = null;
    }
    this.targetFrame = targetFrame;
  }



  /**
   * An accessor method to get the current value of the target frame.
   *
   * @return The current target frame.
   */
  public String getTargetFrame () {
    return this.targetFrame;
  }



  /**
   * This method is used to set the target URL to an actual
   * <CODE>URL</CODE> object.  It is used by the mouse event
   * handler.
   *
   * @param url The <CODE>URL</CODE> to load in the page if
   *        the user clicks on the applet.
   */
  public void setURL (URL url) {
    this.targetURL = url;
    if (this.mouseInside)
      if (this.targetURL == null)
        this.showStatus (TextScroll.STATUS_MSG);
      else
        this.showStatus (this.targetURL.toString ());
  }



  /**
   * Display the given string in the left panel.
   *
   * @param msg The string to display in the left
   *    panel.
   */
  public void setLeftText (String msg) {
    this.leftText = msg;
    this.showLeftText = true;
  }



  /**
   * Answer the currently set target URL.
   *
   * @return The current URL target.
   */
  public URL getURL () {
    return this.targetURL;
  }



  /**
   * Answer a little blurb about this applet.
   *
   * @return info about this applet.
   */
  public String getAppletInfo () {
    return
      "TextScroll  Version " + VERSION +
      "  Copyright (C) 1998 by Kevin Swan, 013639s@dragon.acadiau.ca";
  }



  /**
   * Answer information about legal parameters.
   *
   * @return info about the parameters.
   */
  public String[][] getParameterInfo () {
    String[][] pinfo = {
       { "fontface", "Serif, SansSerif, Monospaced", "The font to use" },
       { "fontsize", "integer", "The size of font to use" },
       { "speed", TextScroll.MIN_SPEED + " - " + TextScroll.MAX_SPEED, "Scroll speed" },
       { "data", "String", "Name of text file to display" },
       { "foreground", "rrr,ggg,bbb", "RGB value to use for foreground color" },
       { "background", "rrr,ggg,bbb", "RGB value to use for background color" }
    };
    return pinfo;
  }



  /**
   * Sets the left foreground color to the given RGB String.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setLeftForegroundColor (String rgb) {
    Color color = getColorFromString (rgb);
    if (color != null)
      this.leftForegroundColor = color;
    this.getBuffGraphics ().setColor (this.leftForegroundColor);
  }



  /**
   * Sets the left background color to the given RGB String.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setLeftBackgroundColor (String rgb) {
    Color color = getColorFromString (rgb);
    if (color != null)
      leftBackgroundColor = color;
    this.getBuffGraphics ().setColor (this.leftBackgroundColor);
    this.getBuffGraphics ().fillRect (0, 0, this.leftWidth, this.buffHeight);
    this.getBuffGraphics ().setColor (this.rightForegroundColor);
  }



  /**
   * Sets the right foreground color to the given RGB String.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setRightForegroundColor (String rgb) {
    Color color = getColorFromString (rgb);
    if (color != null)
      this.rightForegroundColor = color;
    this.getBuffGraphics ().setColor (this.rightForegroundColor);
  }



  /**
   * Sets the right foreground color to the given RGB String.
   * This method is left in for backwards compatibility.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setForegroundColor (String rgb) {
    this.setRightForegroundColor (rgb);
  }



  /**
   * Sets the right background color to the given RGB String.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setRightBackgroundColor (String rgb) {
    Color color = getColorFromString (rgb);
    if (color != null)
      rightBackgroundColor = color;
    this.getBuffGraphics ().setColor (this.rightBackgroundColor);
    this.getBuffGraphics ().fillRect (this.leftWidth,
                                0,
                                this.buffWidth,
                                this.buffHeight);
    this.getBuffGraphics ().setColor (this.rightForegroundColor);
  }



  /**
   * Sets the right background color to the given RGB String.
   * This method is left in for backwards compatibility.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setBackgroundColor (String rgb) {
    this.setRightBackgroundColor (rgb);
  }



  /**
   * Try and pause for the given number of milliseconds.  Note
   * that the time is given as a String.
   *
   * @param time a String that should be able to be converted to an
   *             Integer.
   */
  public void pause (String timeStr) {
    Integer time;
    try {
      time = Integer.valueOf (timeStr);
    } catch (NumberFormatException nfe) {
      System.err.println (
        "Error: Invalid integer specified for pause (): \"" +
        timeStr +
        "\"");
      return;
    }
    try {
      this.scroller.sleep (time.intValue ());
    } catch (InterruptedException ie) {
    }
    return;
  }



  /**
   * This is simply a way for the user to call <CODE>toggle ()</CODE>
   * as a directive.  This was done to relieve confusion, so the user
   * can simply use <CODE>pause ()</CODE> with or without an argument
   * to cause the applet to pause scrolling.  If it is called with
   * no arguments, this method is called, and the scrolling stops
   * until the user clicks the text area.
   *
   */
  public void pause () {
    this.toggle ();
  }



  /**
   * Sets the speed of the applet to the given value.  Note that the
   * speed value should be an integer in the form of a String, between
   * MIN_SPEED and MAX_SPEED.  If an invalid value is specified,
   * the speed value is left at its current setting.
   *
   * @param speedStr a String representing the integer speed to use for
   *        this applet.
   */
  public void setSpeed (String speedStr) {
    Integer speed;
    try {
      speed = Integer.valueOf (speedStr);
    } catch (NumberFormatException nfe) {
      System.err.println (
          "Error: Invalid integer specified for setSpeed (): \"" +
          speedStr +
          "\"");
      return;
    }

    if ((speed.intValue () <= TextScroll.MAX_SPEED) &&
        (speed.intValue () >= TextScroll.MIN_SPEED))
      this.speed = speed.intValue ();

    return;
  }



  /**
   * This method allows the user to change fonts in the middle of
   * the scrolling.  It sets the font to use for the left pane.
   *
   * @param face The <CODE>String</CODE> name of the new
   *             <CODE>Font</CODE> to use in the left pane.
   */
  public void setLeftFontFace (String face) {
    this.leftFont =
        new Font (face, this.leftFont.getStyle (), this.leftFontSize);
  }



  /**
   * This method allows the user to change fonts in the middle of
   * the scrolling.  It sets the font to use for the right pane.
   *
   * @param face The <CODE>String</CODE> name of the new
   *             <CODE>Font</CODE> to use in the right pane.
   */
  public void setRightFontFace (String face) {
    this.rightFont =
        new Font (face, this.rightFont.getStyle (), this.rightFontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont);
    this.fontHeight =
        fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading ();
    this.getBuffGraphics ().setFont (this.rightFont);
  }



  /**
   * This method allows the user to change fonts in the middle of
   * the scrolling.  It sets the font to use for the right pane.
   * This method is left in for backwards compatibility.
   *
   * @param face The <CODE>String</CODE> name of the new
   *             <CODE>Font</CODE> to use in the right pane.
   */
  public void setFontFace (String face) {
    this.setRightFontFace (face);
  }



  /**
   * This method allows the user to change font size
   * in the middle of the scrolling.  It sets the
   * size of the font to use in the left pane.
   *
   * @param sizeStr The <CODE>String</CODE> representing
   *    an integer size of the <CODE>Font</CODE> to use
   *    in the left pane.
   */
  public void setLeftFontSize (String sizeStr) {
    int size;

    try {
      size = Integer.parseInt (sizeStr);
    } catch (NumberFormatException nfe) {
      System.err.println (
           "Error: Invalid integer specified for setLeftFontSize (): \"" +
           sizeStr +
           "\"");
      return;
    }

    this.leftFontSize = size;
    this.leftFont =
           new Font (this.leftFont.getName(),
                     this.leftFont.getStyle (),
                     this.leftFontSize);
  }



  /**
   * This method allows the user to change font size
   * in the middle of the scrolling.  It sets the
   * size of the font to use in the right pane.
   *
   * @param sizeStr The <CODE>String</CODE> representing
   *    an integer size of the <CODE>Font</CODE> to use
   *    in the right pane.
   */
  public void setRightFontSize (String sizeStr) {
    int size;

    try {
      size = Integer.parseInt (sizeStr);
    } catch (NumberFormatException nfe) {
      System.err.println (
           "Error: Invalid arg for setFontSize or setRightFontSize (): \"" +
           sizeStr +
           "\"");
      return;
    }

    this.rightFontSize = size;
    this.rightFont =
           new Font (this.rightFont.getName(),
                     this.rightFont.getStyle (),
                     this.rightFontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont);
    this.fontHeight =
        fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading ();
    this.getBuffGraphics ().setFont (this.rightFont);
  }



  /**
   * This method allows the user to change font size
   * in the middle of the scrolling.  It sets the
   * size of the font to use in the right pane.
   * It is left in for backwards compatibility.
   *
   * @param sizeStr The <CODE>String</CODE> representing
   *    an integer size of the <CODE>Font</CODE> to use
   *    in the right pane.
   */
  public void setFontSize (String sizeStr) {
    this.setRightFontSize (sizeStr);
  }






}
