/*
 * TextScroll_1_0.java  2.5.1 05/04/98
 *
 * Copyright (c) 1997, 1998 by Kevin Swan.  All rights reserved.
 *
 * I reserve all rights to this software.  You may use it and
 * distribute it freely, provided you do not remove this header
 * and attribute partial credit to the author, Kevin Swan.
 *
 * 24 Feb 1998  Added directives to catch up to the Java 1.1 version.
 * 27 Feb 1998  Added setCenter() directive.
 *              Modified the supportedDirective() to use an array.
 *              Released as version 2.2.
 * 27 Feb 1998  Added setBold() and setItalic() directives.
 *              Added an element, inset, for the x coordinate
 *                of text to permit an inset.
 *              Added directive setInset() to set the inset.
 *              Released as version 2.3.
 * 31 Mar 1998  Modified to permit complete URLs in the "data"
 *                parameter of the applet.
 *              Released as version 2.4.
 * 01 Apr 1998  Added setURL() directive.
 *              Added version and credit information to init() method.
 *              Released as version 2.5.
 * 05 Apr 1998  Modified so that if a URL has been set using setURL(),
 *                then when the user moves their mouse over the applet,
 *                the target URL is displayed in the status bar, instead
 *                of "Click to start/stop the applet."  If no URL is set,
 *                it still displays "Click to ..."
 */



import java.util.Vector;
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;



/**
 * This is the Java 1.0 version of the same applet.
 *
 * <P>
 * This applet is displays an attractive scrolling text window.  It is very
 * simple in design, but allows for a great deal of configuration.  The user
 * can specify multiple parameters with the <CODE>&lt;PARAM&gt;</CODE> tags,
 * such as the following:
 *
 * <P>
 * <TABLE BORDER>
 *   <TR>
 *     <TD BGCOLOR="#E0E0FF"><B>fontface</B></TD>
 *     <TD>The style of <CODE>Font</CODE> to use.  It must be a type
 *         supported by Java.  If an invalid font face is specified,
 *         <CODE>SansSerif</CODE> will be used.  <CODE>SansSerif</CODE>
 *         is also the default font face.</TD>
 *   </TR>
 *   <TR>
 *     <TD BGCOLOR="#E0E0FF"><B>fontsize</B></TD>
 *     <TD>The size of <CODE>Font</CODE> to use.  If an invalid size is
 *         specified, the default will be used.  The default font size is
 *         10.</TD>
 *   </TR>
 *   <TR>
 *     <TD BGCOLOR="#E0E0FF"><B>speed</B></TD>
 *     <TD>The speed of scrolling to use.  Valid values are all numbers
 *         from <CODE>TextScroll_1_0.MIN_SPEED</CODE> to
 *         <CODE>TextScroll_1_0.MAX_SPEED</CODE>, with 
 *         <CODE>TextScroll_1_0.MIN_SPEED</CODE> being the slowest and
 *         <CODE>TextScroll_1_0.MAX_SPEED</CODE> being the fastest.  The
 *         default is <CODE>TextScroll_1_0.DEFAULT_SPEED</CODE>.</TD>
 *   </TR>
 *   <TR>
 *     <TD BGCOLOR="#E0E0FF"><B>data</B></TD>
 *     <TD>The name of the datafile to use.
 *         In versions 2.4 and above, you can use a complete URL in this
 *         parameter if you wish.  If the value of this parameter starts
 *         with &quot;http&quot;, it tries to create a new URL from the
 *         string.  Otherwise, it treats it as a relative URL from the
 *         <CODE>CODEBASE</CODE>.</TD>
 *   </TR>
 *   <TR>
 *     <TD BGCOLOR="#E0E0FF"><B>foreground</B></TD>
 *     <TD>The foreground color for the applet.  This will be the color
 *         of the text.  The default is white.</TD>
 *   </TR>
 *   <TR>
 *     <TD BGCOLOR="#E0E0FF"><B>background</B></TD>
 *     <TD>The background color for the applet.  The default is black.
 *     </TD>
 *   </TR>
 * </TABLE>
 *
 * <P>Note all of the parameters can be specified in the text file
 * itself, with the exception of the name of the text file.  Thus, the
 * only mandatory <CODE>&lt;PARAM&gt;</CODE> tag is the <CODE>data</CODE>
 * tag.  Indeed, this is the only one I usually specify, since the data
 * file changes most of the other values several times during the
 * presentation.
 *
 * <P>If the user clicks on the scrolling text, the scrolling will stop.
 * If the user clicks again, it will start again, in the same place.
 *
 * <P><B>Developer Notes:</B>
 * <BLOCKQUOTE>
 *   This section describes how the applet works.  If you are not
 *   interested, you can move ahead to the API.
 *
 *   <P>Basically, the applet creates an offscreen <CODE>Image</CODE>
 *   which is uses for double-buffering.  The applet writes the text
 *   to the bottom of this area, and copies it onto the active canvas.
 *   When the text just written is completely visible, another line
 *   it output.   A thread constantly copies the window up the onscreen
 *   <CODE>Image</CODE> and updates the applet display.  Pretty basic.
 * </BLOCKQUOTE>
 *
 * @version 2.5.1, 05 Apr 1998
 * @author Kevin Swan, 013639s@dragon.acadiau.ca
 */

public class TextScroll_1_0 extends Applet implements Runnable {

  /**
   * The current version of this applet.
   */
  public final String VERSION = "2.5.1";

  /**
   * A constant representing the maximum number of milliseconds
   * the applet may sleep.  This is the maximum number a user may
   * specify as the speed value, and would represent a sleep time
   * of MIN_SPEED milliseconds.
   */
  public static final int MAX_SPEED = 100;

  /**
   * A constant representing the minimum number of milliseconds
   * the applet may sleep.  This is the minimum number a user may
   * specify as the speed value, and would represent a sleep time
   * of MAX_SPEED milliseconds.
   */
  public static final int MIN_SPEED = 1;

  /**
   * A constant representing the default number of milliseconds
   * the applet should sleep.  This value is used for the speed
   * if the user specifies an invalid value, or none at all.
   */
  public static final int DEFAULT_SPEED = 70;

  /**
   * The set of supported directives.
   */
  public static final String[] directives = {
    "setForegroundColor",
    "setBackgroundColor",
    "setSpeed",
    "pause",
    "setFontFace",
    "setFontSize",
    "setCenter",
    "setBold",
    "setItalic",
    "setInset",
    "setURL"
  };



  /** The Image to create and use for buffering. */
  private Image buffImage;

  /** The Graphics object from the buffer Image that we can draw on. */
  private Graphics buffGraphics;

  /** The Colors to use. */
  private Color backgroundColor, foregroundColor;

  /** The Font to use. */
  private Font font;

  /** The size of the Font we're using. */
  private int fontSize;

  /** The descent of the Font we're using. */
  private int descent;

  /** The name of the file to load. */
  private String fileName;

  /** The height and width of the applet/clipping window. */
  private int width, height;

  /** Whether we should center the text or not. */
  private boolean center = false;

  /**
   * Holds current style setting, using constants declared in
   * java.awt.Font.
   */
  private int style = Font.PLAIN;

  /** The number of lines of text to display. */
  private int numLines;

  /**
   * The speed to scroll at.  Can be from MIN_SPEED to MAX_SPEED.
   * Default is DEFAULT_SPEED.
   */
  private int speed;

  /**
   * This is the target URL the applet should load if the user
   * clicks on the applet.
   */
  private URL targetURL = null;

  /** The x coordinate to start drawing text at. */
  private int inset = 3;

  /** boolean to flag if the Applet is currently running. */
  private boolean running;

  /**
   * The vertical increment.  This is how far down to print the next
   * line of text.
   */
  private int verticalIncrement;

  /** The actual text to display, one line per entry. */
  private String[] text;

  /** The "window" we're displaying in, used for scrolling. */
  private int frame = 16;

  /** The current line to append when we have to add another line. */
  private int currLine = 0;

  /** Thread to control the scrolling. */
  private Thread scroller;

  /**
   * This is an undocumented element to allow greater control over
   * the positioning of the animation.  This is how many pixels to
   * move the text when scrolling.  The default is 1.
   */
  private int offset = 1;

  /**
   * This variable remebers whether the mouse is inside the applet or
   * not.  This is important because if it *is*, and we had a URL set,
   * then that URL is displayed in the status bar.  If the mouse stays
   * inside the applet, and the target URL changes, that should be
   * reflected in the status bar.  So, when setURL() is called, it must
   * check this flag to see if it should update the status bar.
   */
  private boolean mouseInside = false;



  /**
   * Called during initialization.  This is where we check all run-time
   * flags set by the <CODE>&lt;PARAM&gt;</CODE> tags, and load the
   * text.  We will also prepare the text buffer.
   */
  public void init () {

    /* Print version info. */
    System.err.println ("TextScroll_1_0  v" + TextScroll_1_0.VERSION +
         "\nCopyright (C) 1998   Kevin Swan, 013639s@dragon.acadiau.ca");

    running = false;

    String param = null;

    /*
     * Get the dimensions of the applet.
     */
    this.width  = this.size ().width;
    this.height = this.size ().height;


    /*
     * Get the color parameters for the text area.
     */
    param = getParameter ("foreground");
    if (param == null)
      foregroundColor = Color.black;
    else
      foregroundColor = getColorFromString (param);
    if (foregroundColor == null)
      foregroundColor = Color.black;

    param = getParameter ("background");
    if (param == null)
      backgroundColor = Color.white;
    else
      backgroundColor = getColorFromString (param);
    if (backgroundColor == null)
      backgroundColor = Color.white;


    /*
     * Get the desired Font information.
     */

    param = getParameter ("fontsize");
    if (param == null)
      this.fontSize = 10;
    else
      try {
        this.fontSize = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.fontSize = 10;
      }

    String face = null;

    param = getParameter ("fontface");
    if (param == null)
      face = "SansSerif";
    else
      face = param;

    this.font = new Font (face, this.style, this.fontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.font);
    verticalIncrement = fm.getHeight ();
    this.descent = fm.getMaxDescent ();

    /*
     * Set the name of the file to load.
     */
    param = getParameter ("data");
    if (param == null)
      fileName = null;
    else
      fileName = param;

    /* 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_1_0.DEFAULT_SPEED;
    else
      try {
        this.speed = Integer.parseInt (param);
      } catch (NumberFormatException nfe) {
        this.speed = TextScroll_1_0.DEFAULT_SPEED;
      }

    if (this.speed > TextScroll_1_0.MAX_SPEED || this.speed < TextScroll_1_0.MIN_SPEED)
      this.speed = TextScroll_1_0.DEFAULT_SPEED;


    this.resize (this.width, this.height);

    /*
     * buffGraphics is what we will use to draw on our buffImage, the
     * offscreen canvas we will use to write the text to before clipping
     * it and copying it to the active Graphics object in paint ().
     */
    this.buffImage = this.createImage (this.width, this.height + 2 * verticalIncrement);
    this.buffGraphics = this.buffImage.getGraphics ();

    /* Prepare the offscreen buffer. */
    this.buffGraphics.setColor (this.backgroundColor);
    this.buffGraphics.fillRect (0, 0, this.width, this.height + 2 * verticalIncrement);

    this.buffGraphics.setColor (this.foregroundColor);
    this.buffGraphics.setFont (this.font);

    loadData ();

  } /* init () */



  /**
   * This method attempts to convert a <CODE>String</CODE> numeric
   * representation of a color into an actual <CODE>Color</CODE> object.
   * It expects the given <CODE>String</CODE> to be in the format
   * &quot;<CODE>red,green,blue</CODE>&quot;
   *
   * @param rgb A comma-separated RGB numeric representation of a
   *            color, passed as a <CODE>String</CODE>.
   *
   * @return A <CODE>Color</CODE> object represented by the given RGB value,
   *         if the values are legal.  If an error occurs, return
   *         <CODE>null</CODE>.
   */
  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;
    }
  } /* getColorFromString () */



  /**
   * The implementation of the <CODE>run ()</CODE> method, as required by
   * implementing the <CODE>Runnable</CODE> interface.  This method performs
   * the actual animation.
   */
  public void run () {

    while (running) {
      /* Move the area upwards offset pixels. */
      buffGraphics.copyArea (0, 1, width, height + 2 * verticalIncrement, 0, 0 - offset);
      frame -= offset;

      /* The frame tells us when its time to draw another line of text. */
      if (frame < (0 - descent)) {
        /* Get the next line in the source file. */
        String line = this.text [this.currLine];
        this.currLine++;
        if (this.currLine == this.numLines)
          this.currLine = 0;

        /*
         * If the line is a method directive, try to honour it.  This
         * is one of the major differences between this version, for Java
         * 1.0, and the standard Java 1.1 version.  The standard version
         * can use reflection here.  We cannot use this here, so we must
         * resort to a less elegant method.  In this version, if we find
         * a directive, we will simply farm it off to another instance
         * method which will do String comparisons to try and service
         * the directive.
         */
        if (line.startsWith ("^^")) {

          /* Strip off the leading "^^" */
          line = line.substring (2, line.length ());

          this.serviceDirective (line);

          continue;

        }

        /* Treat the line as normal. */

        frame = verticalIncrement;
        if (!this.center)
          buffGraphics.drawString (line,
                                   this.inset,
                                   this.height + verticalIncrement);
        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.
           */
          FontMetrics fm = this.getToolkit ().getFontMetrics (this.font);
          int length = fm.stringWidth (line);
          int start = (this.width / 2) - (length / 2);
          start = (start < 0) ? 0 : start;
          buffGraphics.drawString (line, start, this.height + verticalIncrement);
        }
      }

      try {
        scroller.sleep (TextScroll_1_0.MAX_SPEED + 1 - this.speed);
      } catch (InterruptedException ie) {
      }

      this.repaint ();
    }

  } /* run () */



  /**
   * This method loads the textual data that we want to display from over
   * the network.  It is called by <CODE>init()</CODE>.
   */
  private void loadData () {

    if (fileName == null) {
      this.numLines = 1;
      this.text = new String[this.numLines];
      this.text[0] = "No \"data\" parameter specified.";
      return;
    }

    URL dataFile = null;
    URLConnection dataConnection = null;
    DataInputStream dis = null;

    try {
      if (!fileName.startsWith ("http"))
        dataFile = new URL (getCodeBase (), fileName);
      else
        dataFile = new URL (fileName);
    } catch (MalformedURLException mue) {
      this.numLines = 1;
      this.text = new String[this.numLines];
      this.text[0] = "Caught MalformedURLException accessing data file.";
      return;
    }

    try {
      dataConnection = dataFile.openConnection ();
    } catch (IOException ioe) {
      this.numLines = 1;
      this.text = new String[this.numLines];
      this.text[0] = "Caught IOException opening a connection to the data file.";
      return;
    }

    try {
      dis = new DataInputStream (dataConnection.getInputStream ());
    } catch (IOException ioe) {
      this.numLines = 1;
      this.text = new String[this.numLines];
      this.text[0] = "Caught IOException opening stream on data file.";
      return;
    }

    String line = null;
    Vector data = new Vector ();
    int index;

    this.showStatus ("Retrieving data ...");

    while (true) {
      try {
        line = dis.readLine ();
      } catch (IOException ioe) {
        /* Hope we're at EOF */
        break;
      }

      if (line == null)
        break;

      data.addElement (line);

    } // while

    this.numLines = data.size ();
    this.text = new String[this.numLines];
    for (int i = 0 ; i < data.size () ; i++)
      this.text[i] = new String ((String) data.elementAt (i));


    try {
      dis.close ();
    } catch (IOException ioe) {
      System.err.println ("IOException closing DataInputStream on text data.");
    }

    this.showStatus ("");

  } /* loadData () */



  /**
   * Called to paint the screen.
   */
  public void paint (Graphics g) {
    g.drawImage (this.buffImage, 0, 0, this);
  }



  /**
   * Called to update the screen.
   */
  public void update (Graphics g) {
    g.drawImage (this.buffImage, 0, 0, this);
  } 



  /**
   * 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 ();
  }



  /**
   * Stop the applet.
   */
  public void stop () {
    running = false;
    if (scroller != null)
      scroller.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 (TextScroll_1_0.this.getURL () == null)
      TextScroll_1_0.this.showStatus ("Click to start/stop the applet.");
    else
      TextScroll_1_0.this.showStatus (TextScroll_1_0.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)
          this.getAppletContext ().showDocument (this.getURL ());
      } catch (Exception e) {
        this.toggle ();
      }
    return true;
  }



  /**
   * This method is unique to the Java 1.0 version.  This is how we
   * will handle directives, since we cannot take advantage of
   * the reflection API.
   *
   * @param command The directive we are to service, without the
   *            &quot;^^&quot; characters at the front.  This is simply
   *            a method name, followed by 0 or more whitespace,
   *            followed by a ( character, then a string argument, then
   *            a ) character.
   */
  public void serviceDirective (String command) {
    String methodName = command.substring (0, command.indexOf ('('));
    methodName = methodName.trim ();

    if (!supportedDirective (methodName)) {
      System.err.println ("Unsupported directive: \"" + methodName + "\"");
      return;
    }

    String arg = command.substring (command.indexOf ('(') + 1, command.indexOf(')'));

    if (methodName.equals ("setForegroundColor"))
      this.setForegroundColor (arg);
    else if (methodName.equals ("setBackgroundColor"))
      this.setBackgroundColor (arg);
    else if (methodName.equals ("setSpeed"))
      this.setSpeed (arg);
    else if (methodName.equals ("pause"))
      if (arg.length () > 0)
        this.pause (arg);
      else
        this.pause ();
    else if (methodName.equals ("setFontFace"))
      this.setFontFace (arg);
    else if (methodName.equals ("setFontSize"))
      this.setFontSize (arg);
    else if (methodName.equals ("setCenter"))
      this.setCenter (arg);
    else if (methodName.equals ("setBold"))
      this.setBold (arg);
    else if (methodName.equals ("setItalic"))
      this.setItalic (arg);
    else if (methodName.equals ("setInset"))
      this.setInset (arg);
    else if (methodName.equals ("setURL"))
      this.setURL (arg);
    else
      System.err.println ("Logic Error!  \"" + methodName + "\" was reported to be legal, but could not be handled!");

    return;
  }



  /**
   * This method is used to specify whether the text 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 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.center = true;
    else if (boolStr.equalsIgnoreCase ("false"))
      this.center = false;
    else
      System.err.println ("setCenter directive ignored - invalid argument: " +
               boolStr);
  }



  /**
   * This method is used to turn bold font styling 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 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) {
    if (boolStr.equalsIgnoreCase ("true"))
      this.style = this.style | Font.BOLD;
    else if (boolStr.equalsIgnoreCase ("false"))
      this.style = this.style & ~Font.BOLD;
    else {
      System.err.println ("setBold directive ignored - invalid argument: " +
               boolStr);
      return;
    }
    this.font = new Font (this.font.getName (), this.style, this.fontSize);
    this.buffGraphics.setFont (this.font);
  }



  /**
   * This method is used to turn italic font styling 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 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) {
    if (boolStr.equalsIgnoreCase ("true"))
      this.style = this.style | Font.ITALIC;
    else if (boolStr.equalsIgnoreCase ("false"))
      this.style = this.style & ~Font.ITALIC;
    else {
      System.err.println ("setItalic directive ignored - invalid argument: " +
               boolStr);
      return;
    }
    this.font = new Font (this.font.getName (), this.style, this.fontSize);
    this.buffGraphics.setFont (this.font);
  }



  /**
   * 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.
   *
   * @param URLString The URL to load in the page the applet is
   *        being displayed in, or &quot;null.&quot;
   */
  public void setURL (String URLString) {
    if (URLString.equalsIgnoreCase ("null"))
      this.setURL ((URL) 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 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 ("Click to start/stop the applet.");
      else
        this.showStatus (this.targetURL.toString ());
  }



  /**
   * Answer the currently set target URL.
   *
   * @return The current URL target.
   */
  public URL getURL () {
    return this.targetURL;
  }



  /**
   * This method is used to determine whether or not a directive the
   * user is attempting to use is supported.  This is done for security
   * reasons, to protect the user from accidentally calling methods
   * they shouldn't be in Applet and its superclasses.
   *
   * @param methodName the name of the method the user is trying to use.
   *
   * @return true if the user is permitted to call the named method,
   *         false otherwise.
   */
  public static boolean supportedDirective (String methodName) {

    for (int i = 0 ; i < directives.length ; i++)
      if (directives[i].equals (methodName))
        return true;
    return false;
  }



  /**
   * Answer a little blurb about this applet.
   *
   * @return info about this applet.
   */
  public String getAppletInfo () {
    return
      "TextScroll_1_0  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_1_0.MIN_SPEED + " - " + TextScroll_1_0.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 foreground color to the given RGB String.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setForegroundColor (String rgb) {
    Color color = getColorFromString (rgb);
    if (color != null)
      foregroundColor = color;
    this.buffGraphics.setColor (this.foregroundColor);
  }



  /**
   * Sets the background color to the given RGB String.
   *
   * @param rgb a String representing a comma-seperated RGB value.
   */
  public void setBackgroundColor (String rgb) {
    Color color = getColorFromString (rgb);
    if (color != null)
      backgroundColor = color;
    this.buffGraphics.setColor (this.backgroundColor);
    this.buffGraphics.fillRect (0, 0, this.width, this.height + 2 * verticalIncrement);
    this.buffGraphics.setColor (this.foregroundColor);
  }



  /**
   * 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_1_0.MAX_SPEED) && (speed.intValue () >= TextScroll_1_0.MIN_SPEED))
      this.speed = speed.intValue ();

    return;
  }



  /**
   * This method allows the user to change fonts in the middle of
   * the scrolling.
   *
   * @param face The <CODE>String</CODE> name of the new
   *             <CODE>Font</CODE> to use.
   */
  public void setFontFace (String face) {
    this.font = new Font (face, this.style, this.fontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.font);
    this.verticalIncrement = fm.getHeight ();
    this.descent = fm.getMaxDescent ();
    this.buffGraphics.setFont (this.font);
  }



  /**
   * This method allows the user to change font size in the middle of
   * the scrolling.
   *
   * @param sizeStr The <CODE>String</CODE> representing an integer size
   *                of the <CODE>Font</CODE> to use.
   */
  public void setFontSize (String sizeStr) {
    int size;

    try {
      size = Integer.parseInt (sizeStr);
    } catch (NumberFormatException nfe) {
      System.err.println ("Error: Invalid integer specified for setFontSize (): \"" + sizeStr + "\"");
      return;
    }

    this.fontSize = size;
    this.font = new Font (this.font.getName(), this.style, this.fontSize);
    FontMetrics fm = this.getToolkit ().getFontMetrics (this.font);
    this.verticalIncrement = fm.getHeight ();
    this.descent = fm.getMaxDescent ();
    this.buffGraphics.setFont (this.font);
  }

}
