home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1997 October / PCO1097.ISO / FilesBBS / FREI / FSCROLL.EXE / SRC / FunScroll.java next >
Encoding:
Java Source  |  1997-09-02  |  37.9 KB  |  1,339 lines

  1. /*
  2.  * Copyright (c) 1996 by Jan Andersson, Torpa Konsult AB.
  3.  *
  4.  * Permission to use, copy, and distribute this software for
  5.  * NON-COMMERCIAL purposes and without fee is hereby granted
  6.  * provided that this copyright notice appears in all copies.
  7.  */
  8. import java.applet.*;
  9. import java.awt.*;
  10. import java.awt.image.*;
  11. import java.util.*;
  12. import java.io.*;
  13. import java.net.*;
  14.  
  15. /**
  16.  * FunScroll - A Funnier (?) scrolling text/image applet.
  17.  *
  18.  * @version 1.35 97/09/01
  19.  * @author  Jan Andersson (janne@torpa.se)
  20.  * 
  21.  */
  22. public class FunScroll extends Applet implements Runnable 
  23. {            
  24.    static final int MaxLines = 50; // max no. of line parameters
  25.    static final int ShadowIn = 0;  // frame types:
  26.    static final int ShadowOut = 1;
  27.    static final int ShadowEtchedIn = 2;
  28.    static final int ShadowEtchedOut = 3;
  29.    
  30.    Image bgImage = null;    // backgound image
  31.    Image tiledBgImage = null;    // tiled backgound image
  32.    MediaTracker mediaTracker;   // to track loading of backgound image
  33.    Thread thread = null;    // animation thread
  34.    ThreadGroup threadGroup = null; // animation thread group
  35.    boolean suspended = false;    // animation thread suspended
  36.    int threadDelay = 100;    // animation thread delay 
  37.    String lineData = null;    // line data file (url)
  38.    boolean usingCgi = false;    // if line data file is CGI
  39.    boolean randomize = false;    // true will randomly select line to display
  40.    boolean showAbout = true;    // show "about" popup on shift-click
  41.    int updateInterval = 0;    // update interval (to read data file)
  42.    int animateCount = 0;    // reload data (from data file)
  43.    Font font = null;        // default font
  44.    int dx = 3;            // delta x
  45.    int dy = 2;            // delta y
  46.    String delim = null;        // text attribute delimiters
  47.    long threadStartTime;    // time thread started
  48.    Vector animatedObjects = null;    // animated objects
  49.    Vector urlStrings = null;    // associated url's
  50.    String urlTarget = null;    // target widow or frame
  51.    int noOfObjects = 0;        // number of objects
  52.    int currentObjectIndex = 0;    // current object index
  53.    FunScrollAnimate currentObj;    // current object instance
  54.    int frameWidth = 0;        // frame width
  55.    int frameType = ShadowIn;    // frame type
  56.    int frameMargin = 0;        // frame margin
  57.    Color frameDark1 = null;    // darker frame color
  58.    Color frameDark2 = null;    // (slightly) darker frame color
  59.    Color frameBright = null;    // brighter frame color
  60.    Image offImage;        // "off-screen" image
  61.    Dimension offSize;        // "off-screen" size
  62.    Dimension animAreaSize;        // anim. area size
  63.    Hashtable urlParameters = null; // parameters scanned from data file
  64.    protected boolean initiated = false;    // true when initiated
  65.    
  66.    static final boolean debug = false;// debug flag
  67.    static final String sourceLocation =
  68.           "http://www.algonet.se/~jannea/FunScroll/FunScroll.html";
  69.    static final String versionInfo =
  70.           "FunScroll 3.2";
  71.    
  72.    /**
  73.     * Init applet
  74.     */
  75.    public void init()
  76.    {
  77.       // we need a few parameters to draw the initial message...
  78.  
  79.       // get color parameters
  80.       Color fg = readColor(getParameter("fgColor"), Color.black);
  81.       Color bg = readColor(getParameter("bgColor"), getBackground());
  82.       setBackground(bg);
  83.       setForeground(fg);
  84.  
  85.       // get current Thread group
  86.       threadGroup = Thread.currentThread().getThreadGroup();
  87.    }
  88.  
  89.    /**
  90.     * Init parameters and create animated text instances
  91.     */
  92.    public void initParameters()
  93.    {
  94.       Vector lineVector = new Vector();
  95.       urlStrings = new Vector();
  96.  
  97.       // cgi/lineData parameter
  98.       String par = getParameter("cgi");
  99.       if (par != null) {
  100.      usingCgi = true;
  101.       }
  102.       else {
  103.      par = getParameter("lineData");
  104.       }
  105.       if (par != null) {
  106.      lineData = par;
  107.      initLineParametersFromInputURL(lineData, lineVector, urlStrings);
  108.      par = getParameter("updateInterval");
  109.      if (par != null) 
  110.         updateInterval = Integer.valueOf(par).intValue();
  111.       }
  112.       else 
  113.      initLineParameters(lineVector, urlStrings);
  114.  
  115.       // get color parameters
  116.       Color fg = readColor(getParameter("fgColor"), Color.black);
  117.       Color bg = readColor(getParameter("bgColor"), getBackground());
  118.       setBackground(bg);
  119.       setForeground(fg);
  120.  
  121.       // init frame (border) params
  122.       frameWidth = getParameterToInt("frameWidth", frameWidth);
  123.       frameMargin = getParameterToInt("frameMargin", frameMargin);
  124.       frameType = getParameterToFrameType("frameType", frameType);
  125.       
  126.       // get frame/window target
  127.       urlTarget = getParameter("target");
  128.  
  129.       // get background image
  130.       par = getParameter("bgImage");
  131.       if (par != null) {
  132.      bgImage = getImage(getCodeBase(), par);
  133.      mediaTracker = new MediaTracker(this);
  134.      mediaTracker.addImage(bgImage, 0);
  135.       }
  136.  
  137.       // get font parameters
  138.       font = getParameterToFont("font", "style", "size");
  139.  
  140.       // get delay value
  141.       threadDelay = getParameterToInt("delay", threadDelay);
  142.  
  143.       // get dx/dy movement
  144.       dx = getParameterToInt("dx", dx);
  145.       dy = getParameterToInt("dy", dy);
  146.  
  147.       // get delimiters string
  148.       delim = getParameter("delim");
  149.  
  150.       // randomize?
  151.       par = getParameter("randomize");
  152.       if (par != null && par.equalsIgnoreCase("True"))
  153.      randomize = true;
  154.  
  155.       // show about?
  156.       par = getParameter("showAbout");
  157.       if (par != null && par.equalsIgnoreCase("False"))
  158.      showAbout = false;
  159.       
  160.       // create animated texts
  161.       createAnimates(lineVector,
  162.               font, getForeground(), getBackground(),
  163.               dx, dy, delim);
  164.       initiated = true;
  165.    }
  166.  
  167.    /**
  168.     * Gets a parameter of the applet.
  169.     *
  170.     * Use this function to overload java.applet.Applet.getParameter
  171.     * to handle ISO Latin 1 characters correctly in Netscape 2.0.
  172.     * Following a suggestion from Peter Sylvester,
  173.     * Peter.Sylvester@edelweb.fr.
  174.     *
  175.     * Note: this is a work-a-round for a bug in Netscape and should
  176.     *       be removed!
  177.     */
  178.    public String getParameter(String s) {
  179.       // 1'st - get parameter from applet (HTML)
  180.       String par = super.getParameter(s);
  181.  
  182.       // next - get parameter from input URL
  183.       if (urlParameters != null) {
  184.      String par2 = (String)urlParameters.get(s.toLowerCase());
  185.      if (par2 != null)
  186.         par = par2;
  187.       }
  188.       
  189.       if (par == null) 
  190.      return null;
  191.       
  192.       // hack to handle ISO Latin 1 characters correctly in Netscape 2.0.
  193.       //  work-a-round for a bug in Netscap 2.0!
  194.       char ec[] = par.toCharArray();
  195.       for (int i=0; i < ec.length; i++) {
  196.      if (ec[i] >= 0xff00) 
  197.         ec[i] &= 0x00ff ;
  198.       }
  199.       return(new String(ec)) ;
  200.    }
  201.  
  202.      /**
  203.     * Parameter support functions:
  204.     */
  205.    public int getParameterToInt(String par, int defaultval) {
  206.       String s = getParameter(par);
  207.       if(s == null)
  208.      return defaultval;
  209.       else
  210.      return(Integer.parseInt(s));
  211.    }
  212.    
  213.    public int getParameterToFrameType(String par, int defaultval) {
  214.       String s = getParameter(par);
  215.       if(s == null)
  216.      return defaultval;
  217.       if (s.equalsIgnoreCase("ShadowOut"))
  218.      return ShadowOut;
  219.       else if (s.equalsIgnoreCase("ShadowEtchedIn"))
  220.      return ShadowEtchedIn;
  221.       else if (s.equalsIgnoreCase("ShadowEtchedOut"))
  222.      return ShadowEtchedOut;
  223.       else
  224.      return ShadowIn;
  225.    }
  226.  
  227.    public Font getParameterToFont(String namePar, String stylePar,
  228.                   String sizePar)
  229.    {
  230.       String fontName = getParameter(namePar);
  231.       if (fontName == null)
  232.      fontName = "TimesRoman";
  233.       String fontStyle = getParameter(stylePar);
  234.       int style = Font.BOLD;
  235.       if (fontStyle != null) {
  236.      if (fontStyle.equalsIgnoreCase("plain"))
  237.         style = Font.PLAIN;
  238.      else if (fontStyle.equalsIgnoreCase("bold"))
  239.         style = Font.BOLD;
  240.      else if (fontStyle.equalsIgnoreCase("italic"))
  241.         style = Font.ITALIC;
  242.      else if (fontStyle.equalsIgnoreCase("bolditalic"))
  243.         style = Font.ITALIC|Font.BOLD;
  244.       }
  245.       int size = getParameterToInt(sizePar, 24);
  246.       // make sure fonts are created with the right size
  247.       // Note: size-parameter are plattform dependent and the
  248.       // only way to get the same size on all plattforms is to
  249.       // check the "real" size using FontMetrics.
  250.       // Note: we only loop until "real" size if less than 6
  251.       //       or size differs more that 3 pixels...
  252.       FontMetrics fm;
  253.       int realSize = size;
  254.       dbg("init font...");
  255.       Font font = null;
  256.       do {
  257.      dbg("trying: " + fontName + "," + realSize);
  258.      font = new Font(fontName, style, realSize--);
  259.      fm = getFontMetrics(font);
  260.       } while ((fm.getDescent() + fm.getAscent()) > size &&
  261.            realSize >= size-3 && size >= 6);
  262.       if (realSize < size-3 || size < 6) 
  263.      // assume weird font used... Use parsed size.
  264.      font = new Font(fontName, style, size);
  265.       dbg("using font: " + font.toString());
  266.       return font;
  267.    }
  268.    
  269.    /**
  270.     * Init unparsed line parameters (Vector of Strings) and
  271.     * (possibly) associated url's.
  272.     */
  273.    protected void initLineParameters(Vector lineVector, Vector urlVector)
  274.    {
  275.       // get unparsed line parameters
  276.       dbg("get line parameters...");
  277.       for (int i=0; i<MaxLines; i++) {
  278.      String lineParName = "line" + i;
  279.      String linePar = getParameter(lineParName);
  280.      String urlParName = "url" + i;
  281.      String urlPar = getParameter(urlParName);
  282.      if (linePar != null) {
  283.         dbg("  " + lineParName + ":" + linePar);
  284.         lineVector.addElement(linePar);
  285.         dbg("  " + urlParName + ":" + urlPar);
  286.         urlVector.addElement(urlPar);
  287.      }
  288.       }
  289.  
  290.       if (lineVector.size() <= 0)
  291.      // assume no line parameter provided; use default
  292.      initDefaultLineParameters(lineVector);
  293.    }
  294.  
  295.    /**
  296.     * The current character. Ugly :-(
  297.     */
  298.    private int c;
  299.    
  300.    /**
  301.     * Scan spaces.
  302.     */
  303.    private void skipSpace(InputStream in) throws IOException
  304.    {
  305.       while((c >= 0) &&
  306.         ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'))) {
  307.      c = in.read();
  308.       }
  309.    }
  310.    
  311.    /**
  312.     * Skip comment line
  313.     */
  314.    private void skipCommentLine(InputStream in) throws IOException
  315.    {
  316.       dbg("skipCommentLine()");
  317.       while((c >= 0) && (c != '\n') && (c != '\r')) {
  318.      c = in.read();
  319.       }
  320.    }
  321.    
  322.    /**
  323.     * Scan identifier
  324.     */
  325.    private String scanIdentifier(InputStream in) throws IOException
  326.    {
  327.       StringBuffer buf = new StringBuffer();
  328.       while (true) {
  329.      if (((c >= 'a') && (c <= 'z')) ||
  330.          ((c >= 'A') && (c <= 'Z')) ||
  331.          ((c >= '0') && (c <= '9')) || (c == '_')) {
  332.         buf.append((char)c);
  333.         c = in.read();
  334.      } else {
  335.         return buf.toString();
  336.      }
  337.       }
  338.    }
  339.  
  340.    /**
  341.     * Scan tag
  342.     */
  343.    private Hashtable scanTag(InputStream in) throws IOException {
  344.       Hashtable atts = new Hashtable();
  345.       skipSpace(in);
  346.       while (c >= 0 && c != '>') {
  347.      String att = scanIdentifier(in);
  348.      String val = "";
  349.      skipSpace(in);
  350.      if (c == '=') {
  351.         int quote = -1;
  352.         c = in.read();
  353.         skipSpace(in);
  354.         if ((c == '\'') || (c == '\"')) {
  355.            quote = c;
  356.            c = in.read();
  357.         }
  358.         StringBuffer buf = new StringBuffer();
  359.         while ((c > 0) &&
  360.            (((quote < 0) && (c != ' ') && (c != '\t') 
  361.              && 
  362.              (c != '\n') && (c != '\r') && (c != '>'))
  363.             || ((quote >= 0) && (c != quote)))) {
  364.            buf.append((char)c);
  365.            c = in.read();
  366.         }
  367.         if (c == quote) {
  368.            c = in.read();
  369.         }
  370.         skipSpace(in);
  371.         val = buf.toString();
  372.      }
  373.      atts.put(att.toLowerCase(), val);
  374.      skipSpace(in);
  375.       }
  376.       return atts;
  377.    }
  378.    
  379.    /**
  380.     * Scan input data stream
  381.     */
  382.    private void parseInput(DataInputStream in, Vector lineVector,
  383.                Vector urlVector)
  384.       throws IOException
  385.    {
  386.       c = in.read();
  387.       while (c >= 0) {
  388.      if (c == '#')
  389.         skipCommentLine(in);
  390.      else if (c == '<') {
  391.         // probably applet parameter!
  392.         c = in.read();
  393.         String nm = scanIdentifier(in);
  394.         if (nm.equalsIgnoreCase("param")) {
  395.            // yes; we have a <param tag
  396.            Hashtable t = scanTag(in);
  397.            String att = (String)t.get("name");
  398.            if (att == null) {
  399.           System.out.println(
  400.                      "Warning: <param name=... value=...>" +
  401.              "tag requires name attribute.");
  402.            }
  403.            else {
  404.           String val = (String)t.get("value");
  405.           if (val == null) {
  406.              System.out.println(
  407.             "Warning: <param name=... value=...>" +
  408.             "tag requires value attribute.");
  409.           }
  410.           else {
  411.              if (urlParameters == null)
  412.             urlParameters = new Hashtable();
  413.              dbg("parseInput() put " + att + " = '" + val + "'");
  414.              urlParameters.put(att.toLowerCase(), val);
  415.           }
  416.            }
  417.         }
  418.         else {
  419.            // assume line data; read the rest as such!
  420.            readLineData(in, "<" + nm + ">", lineVector, urlVector);
  421.            in.close();
  422.            return;
  423.         }
  424.      }
  425.      else {
  426.         // assume line data; read the rest as such!
  427.         readLineData(in, "", lineVector, urlVector);
  428.         in.close();
  429.         return;
  430.      }
  431.      if (c == '>')
  432.         c = in.read();
  433.      skipSpace(in);
  434.       }
  435.       in.close();
  436.    }
  437.  
  438.    String readDataLine(DataInputStream is)
  439.       throws IOException
  440.    {
  441.       String line = is.readLine();
  442.       while (line != null &&
  443.          (line.length() == 0 || line.charAt(0) == '#'))
  444.      line = is.readLine();
  445.       return line;
  446.    }
  447.    
  448.    void readLineData(DataInputStream is, String add,
  449.              Vector lineVector, Vector urlVector)
  450.       throws IOException
  451.    {
  452.       String line = null;
  453.       line = add + is.readLine();
  454.       while (line != null) {
  455.      dbg("readLineData: " + line);
  456.      // add to line vector
  457.      lineVector.addElement(line);
  458.      line = readDataLine(is);
  459.      if (line != null && line.length() > 4 &&
  460.          line.substring(0, 4).equalsIgnoreCase("URL:")) {
  461.         // assume url specified; add to URL vector
  462.         urlVector.addElement(line.substring(4));
  463.         line = readDataLine(is);
  464.      }
  465.      else {
  466.         urlVector.addElement(null);
  467.      }
  468.       }
  469.    }
  470.  
  471.  
  472.    /**
  473.     * Init unparsed line parameters (Vector of Strings) and
  474.     * (possibly) associated url's from input file.
  475.     */
  476.    protected void initLineParametersFromInputURL(
  477.       String urlString, Vector lineVector, Vector urlVector) {
  478.  
  479.       if (usingCgi) {
  480.      // post as CGI
  481.      postCGI(urlString, lineVector, urlVector);
  482.      return;
  483.       }
  484.       
  485.       // create URL
  486.       URL url = null;
  487.       DataInputStream is = null;
  488.  
  489.       // 1'st, try URL directly
  490.       try {
  491.      url = new URL(urlString);
  492.      dbg("initLineParametersFromInputURL(): using direct URL: " + url);
  493.      // make sure data isn't cashed
  494.      URLConnection urlc = url.openConnection();
  495.      urlc.setUseCaches(false);
  496.      // open input stream
  497.      is = new DataInputStream(urlc.getInputStream());
  498.       } catch (Exception e) {
  499.      dbg("initLineParametersFromInputURL(): Can't read URL:" + url);
  500.      is = null;
  501.       }
  502.  
  503.       if (is == null) {
  504.      // try URL in context of document
  505.      try {
  506.         url = new URL(getDocumentBase(), urlString);
  507.         dbg("initLineParametersFromInputURL(): using rel. URL:" + url);
  508.         // make sure data isn't cashed
  509.         URLConnection urlc = url.openConnection();
  510.         urlc.setUseCaches(false);
  511.         // open input stream
  512.         is = new DataInputStream(urlc.getInputStream());
  513.      } catch (Exception e) {
  514.         dbg("initLineParametersFromInputURL(): Can't read URL:" + url);
  515.         is = null;
  516.         initURLErrorLineParameters(urlString, lineVector);
  517.         updateInterval = 0;
  518.         return;
  519.      }
  520.       }
  521.  
  522.       // parse
  523.       try {
  524.      parseInput(is, lineVector, urlVector);
  525.       }
  526.       catch (IOException e) {
  527.      // ignore (?)
  528.       }
  529.       
  530.       if (lineVector.size() <= 0) {
  531.      // assume no line parameter provided; use error message
  532.      dbg("initLineParametersFromInputURL(): No lines!");
  533.      initURLErrorLineParameters(urlString, lineVector);
  534.      updateInterval = 0;
  535.       }
  536.    }
  537.  
  538.    public void postCGI(String script, Vector lineVector, Vector urlVector)
  539.    {
  540.       String home = getCodeBase().getHost();
  541.       int port = getCodeBase().getPort();
  542.       if (port == -1)
  543.      port = 80;
  544.       
  545.       dbg("postCGI; home: " + home + "port: " + port);
  546.  
  547.       //create a client socket
  548.       Socket sock;
  549.       try {
  550.      sock = new Socket(home, port);
  551.       }
  552.       catch (Exception e) {
  553.      dbg("postCGI(): Can't create socket:" + e);
  554.      initURLErrorLineParameters(script, lineVector);
  555.      updateInterval = 0;
  556.      return;
  557.       }
  558.       
  559.       OutputStream outp;
  560.       InputStream inp;
  561.       DataOutputStream dataout;
  562.       DataInputStream datain;
  563.  
  564.       // Obtain output stream to communicate with the server
  565.       try {
  566.      outp = sock.getOutputStream();
  567.      inp  = sock.getInputStream();
  568.       }
  569.       catch (Exception e) {
  570.      try {
  571.         sock.close();
  572.      }
  573.      catch (IOException ee) {}
  574.      dbg("postCGI(): Can't create output stream:" + e);
  575.      initURLErrorLineParameters(script, lineVector);
  576.      updateInterval = 0;
  577.      return;
  578.       }
  579.       
  580.       try {
  581.      dataout = new DataOutputStream(outp);
  582.      datain  = new DataInputStream(inp);
  583.       }
  584.       catch (Exception e) {
  585.      try {
  586.         sock.close();
  587.      }
  588.      catch (IOException ee) {;}
  589.      dbg("postCGI(): Can't create output stream:" + e);
  590.      initURLErrorLineParameters(script, lineVector);
  591.      updateInterval = 0;
  592.      return;
  593.       }
  594.       
  595.       // Send http request to server and get return data
  596.       try {
  597.      // HTTP header
  598.      String ctype  = "application/octet-stream";
  599.      dataout.writeBytes("POST " + script + " HTTP/1.0\r\n");
  600.      dataout.writeBytes("Content-type: " + ctype + "\r\n");
  601.      dataout.writeBytes("Content-length: 0\r\n");
  602.      dataout.writeBytes("User-Agent: " + versionInfo + "\r\n");
  603.      dataout.writeBytes("\r\n");         // end of header
  604.      
  605.      // read response headers
  606.      String  line;
  607.      while ((line = datain.readLine()) != null  &&  !line.equals("")) {
  608.         dbg("postCGI(); respose header: " + line);
  609.      }
  610.      
  611.      // read and parse response data
  612.      parseInput(datain, lineVector, urlVector);
  613.       }
  614.       catch (Exception e) {
  615.      dbg("postCGI(): IOException when reading stream!");
  616.       }
  617.       
  618.       if (lineVector.size() <= 0) {
  619.      // assume no line parameter provided; use error message
  620.      dbg("postCGI(): No lines!");
  621.      initURLErrorLineParameters(script, lineVector);
  622.      updateInterval = 0;
  623.       }
  624.       
  625.       // close up shop
  626.       try {
  627.      dataout.close();
  628.      datain.close();
  629.       }
  630.       catch (IOException e) {;}
  631.       try {
  632.      sock.close();
  633.       }
  634.       catch (IOException e) {;}
  635.    }
  636.  
  637.    /**
  638.     * Init default line parameters (Vector of Strings).
  639.     * Used when not line parameters specified.
  640.     */
  641.    protected void initDefaultLineParameters(Vector v) {
  642.       threadDelay = 200;
  643.       v.addElement("<25><typed>" + getAppletInfo());
  644.       v.addElement("<100>No parameters specified!");
  645.    }
  646.  
  647.    /**
  648.     * Init error line parameters (Vector of Strings).
  649.     * Used at error, when trying to get input from URL.
  650.     */
  651.    protected void initURLErrorLineParameters(String url, Vector v) {
  652.       threadDelay = 200;
  653.       v.addElement("<nervous><30><color=#FF0000>Error!");
  654.       v.addElement("<100>Could not read url: " + url);
  655.    }
  656.  
  657.    /**
  658.     * Applet Info.
  659.     */
  660.    public String getAppletInfo() {
  661.       return versionInfo;
  662.    }   
  663.  
  664.    /**
  665.     * Parameter Info.
  666.     */
  667.    public String[][] getParameterInfo() {
  668.       // More should be added...
  669.       String[][] info = {
  670.      {"line<n>", "string", "Message line <n>" },
  671.      {"url<n>",  "string", "URL <n>" },
  672.      {"showAbout","boolean", "Display about popup on shift-click" },
  673.      {"randomize","boolean", "Display message lines in random order" },
  674.      {"target","string", "Default frame or window target" },
  675.      {"lineData","string", "Message line data file" },
  676.      {"cgi","string", "CGI script to be used to read message lines" },
  677.      {"updateInterval",  "int", "Update interval to read data file (0)" },
  678.      {"delim",   "string", "Delimiter string (<>)" },
  679.      {"frameWidth",  "int", "Frame border width (0)" },
  680.      {"frameMargin", "int", "Frame margin width (0)" },
  681.      {"frameType", "string", "Frame type (ShadowIn)" },
  682.      {"font",    "string", "Message font (TimesRoman)" },
  683.      {"style",   "string", "Message font style (bold)" },
  684.      {"size",    "int",    "Message font size (22)" },
  685.      {"delay",   "int",    "Animation delay time in millisec. (100)" },
  686.      {"dx",      "int",
  687.             "No of pixels to move horizontally for each animation (2)" },
  688.      {"dy",      "int",
  689.             "No of pixels to move vertically for each animation (1)" },
  690.      {"fgColor", "string", "Foreground Color" },
  691.      {"bgColor", "string", "Background Color" },
  692.       };
  693.       return info;
  694.    }
  695.    
  696.    /**
  697.     * Convert a Hexadecimal String with RGB-Values to Color
  698.     * Uses aDefault, if no or not enough RGB-Values
  699.     */
  700.    public Color readColor(String aColor, Color aDefault) {
  701.       if (aColor == null)
  702.      return aDefault;
  703.  
  704.       Integer rgbValue = null;
  705.       try {
  706.      if (aColor.startsWith("#")) 
  707.         rgbValue = Integer.valueOf(aColor.substring(1), 16);
  708.      else if (aColor.startsWith("0x")) 
  709.         rgbValue = Integer.valueOf(aColor.substring(2), 16);
  710.      else
  711.         // assume symbolic color name
  712.         rgbValue = Integer.valueOf(FunScrollColorSupport.lookup(aColor), 16);
  713.       } catch (NumberFormatException e) {
  714.      rgbValue = null;
  715.       }
  716.       
  717.       if (rgbValue == null)
  718.      return aDefault;
  719.       
  720.       return new Color(rgbValue.intValue());
  721.    }
  722.  
  723.    
  724.    
  725.    /**
  726.     * Create animated text vector. I.e vector with FunScrollAnimate
  727.     * instances.
  728.     */
  729.    public void createAnimates(Vector lines, Font font,
  730.                    Color fg, Color bg,
  731.                    int dx, int dy,
  732.                    String delim)
  733.    {
  734.       noOfObjects = 0;
  735.       animatedObjects = new Vector(lines.size());
  736.       dbg("Creating Animated Text...");
  737.       for (int i=0; i<lines.size(); i++) {
  738.      dbg("  " + (String) lines.elementAt(i));
  739.          animatedObjects.addElement(
  740.             new FunScrollAnimate(
  741.            this, (String) lines.elementAt(i), font,
  742.            fg, bg, dx, dy, delim));
  743.      noOfObjects++;
  744.       }
  745.       currentObjectIndex = 0;    
  746.       if (randomize)
  747.      currentObjectIndex  = (int)(noOfObjects * Math.random());
  748.       
  749.       currentObj = (FunScrollAnimate)
  750.      animatedObjects.elementAt(currentObjectIndex);
  751.  
  752.       // set offSize to zero to be sure that offImage is re-initiated
  753.       offSize = new Dimension(0,0);
  754.    }
  755.    
  756.  
  757.    /**
  758.     * Animate the texts
  759.     */
  760.    public void animate(Graphics g) {
  761.       // update current text
  762.       if (currentObj.update(g)) {
  763.      // done; get next text
  764.      if (randomize)
  765.         currentObjectIndex  = (int)(noOfObjects * Math.random());
  766.      else
  767.         currentObjectIndex++;
  768.      if (currentObjectIndex >= noOfObjects) {
  769.         // all text lines animated
  770.         if (lineData != null && updateInterval > 0)
  771.            animateCount++;
  772.         currentObjectIndex = 0;
  773.      }
  774.      currentObj = (FunScrollAnimate)
  775.         animatedObjects.elementAt(currentObjectIndex);
  776.      currentObj.reset();
  777.       }
  778.    }
  779.    
  780.    /**
  781.     * Paint tiled background image.
  782.     * Based on code by Tony Kolman, 02/20/96.
  783.     *
  784.     * Note: there are performance problems here.
  785.     */
  786.    public void paintTiledImage(Graphics g, Image im,
  787.                    int offset, int width, int height) {
  788.       if (tiledBgImage == null) {
  789.      int imgw = im.getWidth(null);
  790.      int imgh = im.getHeight(null);
  791.      if (imgw > 0 && imgh > 0) {
  792.         // we have the background image; create tiled background image
  793.         tiledBgImage = createImage(width, height);
  794.         Graphics tiledBgGraphics = tiledBgImage.getGraphics();
  795.         tiledBgGraphics.setColor(getBackground());
  796.         tiledBgGraphics.fillRect(0, 0, width, height);
  797.         for (int x = 0; x < width; x += imgw) {
  798.            for (int y = 0; y < height; y += imgh) {
  799.           tiledBgGraphics.drawImage(im, x, y, null);
  800.            }
  801.         }
  802.         tiledBgGraphics.dispose();
  803.      }
  804.       }
  805.       if (tiledBgImage != null) {
  806.      g.drawImage(tiledBgImage, offset, offset, null);
  807.       }
  808.    }
  809.  
  810.    /**
  811.     * Paint last animation
  812.     */
  813.    public void paint(Graphics g) {
  814.       if (offImage != null)
  815.      // paint the image onto the screen
  816.      g.drawImage(offImage, 0, 0, null);
  817.    }
  818.  
  819.    /**
  820.     * Init "loading..." message
  821.     */
  822.    public void initLoadMessage()
  823.    {
  824.       dbg("initLoadMessage()");
  825.       if (offImage == null) {
  826.      // create the initial offscreen graphics context
  827.      offSize = size();
  828.      offImage = createImage(offSize.width, offSize.height);
  829.       }
  830.       Graphics offGraphics = offImage.getGraphics();
  831.       offGraphics.setColor(getBackground());
  832.       offGraphics.fillRect(0, 0, offSize.width, offSize.height);
  833.       offGraphics.setColor(getForeground());
  834.       offGraphics.drawString("FunScroll: Loading applet...",
  835.                  10, offSize.height/2);
  836.       offGraphics.dispose();
  837.       dbg("initLoadMessage() done");
  838.    }
  839.  
  840.    /**
  841.     * Draw a frame at the specified position.
  842.     */
  843.    protected void drawFrame(Graphics g, int x, int y, int w, int h, 
  844.                 int type, int thickness, int margin)
  845.    { 
  846.       if(thickness <= 0)
  847.      return;
  848.  
  849.       if (frameDark1 == null) {
  850.      // create frame colors from background
  851.      frameDark1 = FunScrollColorSupport.darker(getBackground(), .50);
  852.      frameDark2 = FunScrollColorSupport.darker(getBackground(), .10);
  853.      frameBright = FunScrollColorSupport.brighter(getBackground(), .50);
  854.       }
  855.       
  856.       switch (type) {
  857.       case ShadowOut:
  858.      for(int i=0;i<thickness;i++) {
  859.         // top left
  860.         g.setColor(frameBright);
  861.         drawTopLeftLines(g, x, y, w, h, i, margin);
  862.         // bottom right
  863.         g.setColor(frameDark1);
  864.         drawBottomRightLines(g, x, y, w, h, i, margin);
  865.      }
  866.      break;
  867.       case ShadowEtchedIn:
  868.      for(int i=0;i<thickness;i++) {
  869.         // top left
  870.         if(i == 0)
  871.            g.setColor(frameDark1);
  872.         else if (i == thickness-1)
  873.            g.setColor(frameBright);
  874.         else
  875.            g.setColor(frameDark2);
  876.         drawTopLeftLines(g, x, y, w, h, i, margin);
  877.         
  878.         // bottom right
  879.         if(i == 0)
  880.            g.setColor(frameBright);
  881.         else if (i == thickness-1)
  882.            g.setColor(frameDark1);
  883.         else
  884.            g.setColor(frameDark2);
  885.         drawBottomRightLines(g, x, y, w, h, i, margin);
  886.      }
  887.      break;
  888.       case ShadowEtchedOut:
  889.      for(int i=0;i<thickness;i++) {
  890.         // top left
  891.         if(i == 0)
  892.            g.setColor(frameBright);
  893.         else if (i == thickness-1)
  894.            g.setColor(frameDark1);
  895.         else
  896.            g.setColor(getBackground());
  897.         drawTopLeftLines(g, x, y, w, h, i, margin);
  898.         
  899.         // bottom right
  900.         if(i == 0)
  901.            g.setColor(frameDark1);
  902.         else if (i == thickness-1)
  903.            g.setColor(frameBright);
  904.         else 
  905.            g.setColor(getBackground());
  906.         drawBottomRightLines(g, x, y, w, h, i, margin);
  907.      }
  908.      break;
  909.       default:            // ShadowIn (default)
  910.      for(int i=0;i<thickness;i++) {
  911.         // top left
  912.         g.setColor(frameDark1);
  913.         drawTopLeftLines(g, x, y, w, h, i, margin);
  914.         // bottom right
  915.         g.setColor(frameBright);
  916.         drawBottomRightLines(g, x, y, w, h, i, margin);
  917.      }
  918.       }
  919.       // reset background color
  920.       g.setColor(getBackground());
  921.    }
  922.  
  923.    void drawTopLeftLines(Graphics g,
  924.              int x, int y, int w, int h, int i, int margin)
  925.    {
  926.       g.drawLine(x+margin+i, y+margin+i, x+w-margin-i-1, y+margin+i);
  927.       g.drawLine(x+margin+i, y+margin+i, x+margin+i, y+h-margin-i-1);
  928.    }
  929.  
  930.    void drawBottomRightLines(Graphics g,
  931.                int x, int y, int w, int h, int i, int margin)
  932.    {
  933.       g.drawLine(x+margin+i, y+h-margin-i-1, x+w-margin-i-1, y+h-margin-i-1);
  934.       g.drawLine(x+w-margin-i-1, y+margin+i, x+w-margin-i-1, y+h-margin-i-1);
  935.    }
  936.  
  937.    /**
  938.     * Update applet. This method is called in response to a call to repaint.
  939.     * Overridden from java.awt.Component to not clear background.
  940.     */
  941.    public void update(Graphics g) 
  942.    {
  943.       paint(g);
  944.    }
  945.    
  946.    /**
  947.     * Update one frame of animation
  948.     *
  949.     */
  950.    void updateAnimation()
  951.    {
  952.       if (!initiated) {
  953.      return;
  954.       }
  955.           
  956.       long tm = 0;
  957.       if (debug)
  958.      tm = System.currentTimeMillis();
  959.  
  960.       // get size of applet 
  961.       Dimension d = size();
  962.          
  963.       // Create the offscreen graphics context if required
  964.       if ((offImage == null) ||
  965.       (d.width != offSize.width) ||
  966.       (d.height != offSize.height)) {
  967.      
  968.      // create off-screen graphics context
  969.      offSize = d;
  970.      offImage = createImage(d.width, d.height);
  971.  
  972.      // reset Animated Text item
  973.      currentObj.reset();
  974.       }
  975.       
  976.       Graphics offGraphics = offImage.getGraphics();
  977.      
  978.       int margin = getMargin();
  979.      
  980.       // init text area size
  981.       animAreaSize = new Dimension(d.width-(2*margin),
  982.                   d.height-(2*margin));
  983.      
  984.       // paint frame
  985.       offGraphics.setColor(getBackground());
  986.       offGraphics.fillRect(0, 0, d.width, d.height);
  987.       drawFrame(offGraphics,
  988.         0, 0, d.width, d.height,
  989.         frameType, frameWidth, frameMargin);
  990.       
  991.       // from here on just manipulate the text area, using a
  992.       // clip rectangle.
  993.       offGraphics.clipRect(margin, margin,
  994.                animAreaSize.width, animAreaSize.height);
  995.  
  996.    
  997.       // reset text background
  998.       offGraphics.setColor(getBackground());
  999.       offGraphics.fillRect(frameMargin+frameWidth, frameMargin+frameWidth,
  1000.                animAreaSize.width, animAreaSize.height);
  1001.       
  1002.       if (bgImage != null) {
  1003.      if ((mediaTracker.statusID(0, true) & MediaTracker.COMPLETE) != 0) {
  1004.         // background image loaded; paint it
  1005.         paintTiledImage(offGraphics, bgImage, frameMargin+frameWidth,
  1006.                 animAreaSize.width, animAreaSize.height);
  1007.      }
  1008.      else if (mediaTracker.isErrorID(0)) {
  1009.         System.out.println("Error: Can't load bgImage");
  1010.         bgImage = null;
  1011.      }
  1012.       }
  1013.       
  1014.       // animate text
  1015.       animate(offGraphics);
  1016.       
  1017.       offGraphics.dispose();
  1018.       
  1019.       dbg("time for updateAnimation():" + (System.currentTimeMillis() - tm));
  1020.    }
  1021.  
  1022.    /**
  1023.     * Run the loop. This method is called by class Thread.
  1024.     */
  1025.    public void run() {
  1026.       
  1027.       if (Thread.currentThread() == thread) {
  1028.      thread.setPriority(Thread.MIN_PRIORITY);
  1029.      if (!initiated) {
  1030.         // init parameters, once!
  1031.         
  1032.         // But first, init "loading..." message
  1033.         initLoadMessage();
  1034.  
  1035.         // Repaint, to display "loading..." message
  1036.         repaint();
  1037.         // sleep to allow the "loading.." message to be painted
  1038.         try {
  1039.            Thread.sleep(50);
  1040.         } catch (InterruptedException e) {}
  1041.         initParameters();
  1042.      }
  1043.       }
  1044.       
  1045.       while (Thread.currentThread() == thread) {
  1046.      // Update one frame of animation
  1047.      updateAnimation();
  1048.  
  1049.      // Repaint. I.e call update() to paint background image
  1050.      repaint();
  1051.      
  1052.      // Delay depending on how far we are behind (to assure
  1053.          // we really delay as much as requested).
  1054.      try {
  1055.         threadStartTime += threadDelay;
  1056.         int delay = (int) Math.max(
  1057.            threadDelay/2, threadStartTime - System.currentTimeMillis());
  1058.         dbg("Sleep:" + delay);
  1059.         Thread.sleep(delay);
  1060.      } catch (InterruptedException e) {
  1061.         break;
  1062.      }
  1063.      
  1064.      // reload data from URL if required
  1065.      if (lineData != null && updateInterval > 0) {
  1066.         if (animateCount >= updateInterval) {
  1067.            // reaload line data from URL
  1068.            dbg("Re-init data from URL");
  1069.            animateCount = 0;
  1070.            Vector lineVector = new Vector();
  1071.            initLineParametersFromInputURL(
  1072.           lineData, lineVector, urlStrings);
  1073.            createAnimates(lineVector, font,
  1074.                    getForeground(), getBackground(),
  1075.                    dx, dy, delim);
  1076.         }
  1077.      }
  1078.       }
  1079.    }
  1080.    
  1081.    /**
  1082.     * Start the applet by forking an animation thread.
  1083.     */
  1084.    public void start() {
  1085.       repaint();
  1086.       if (thread == null) {
  1087.      // create new animate thread (using thread-group saved in init)
  1088.      if (threadGroup != null)
  1089.         thread = new Thread(threadGroup, this);
  1090.      else
  1091.         thread = new Thread(this);
  1092.      thread.start();
  1093.       }
  1094.       // remember the thread start time
  1095.       threadStartTime = System.currentTimeMillis();
  1096.    }
  1097.    
  1098.    /**
  1099.     * Stop the applet. The thread will exit because run() exits.
  1100.     */
  1101.    public void stop() {
  1102.       thread = null;
  1103.    }
  1104.    
  1105.    /**
  1106.     * Take care of mouse-up event to handle Suspend/Resume
  1107.     * and to show about info.
  1108.     */
  1109.    public boolean mouseUp(Event evt, int x, int y) {
  1110.  
  1111.       if ((evt.modifiers & Event.SHIFT_MASK) != 0) {
  1112.      showAboutPopup();
  1113.      return true;
  1114.       }
  1115.       
  1116.       String urlString = null;
  1117.       if (currentObjectIndex < urlStrings.size())
  1118.      urlString = (String) urlStrings.elementAt(currentObjectIndex);
  1119.       
  1120.       // handle Suspend/Resume
  1121.       // Note: Netscape 3.0 doesnt like Thread.suspend() so, im
  1122.       //       now existing the thread instead...
  1123.       if (suspended || thread == null) {
  1124.      start();
  1125.      suspended = false;
  1126.       }
  1127.       else if (urlString == null) {
  1128.      stop();
  1129.      suspended = true;
  1130.       }
  1131.       
  1132.       if (suspended)
  1133.      // show version when suspended (sneak promotion ;-)
  1134.      showStatus(getAppletInfo() + " - Click to resume.");
  1135.       else {
  1136.      if (urlString != null) {
  1137.         // show document as specified in urlString
  1138.         showDocument(urlString);
  1139.      }
  1140.      else {
  1141.         // sneak advertising and about about popup...
  1142.         if (showAbout)
  1143.            showStatus(getAppletInfo() + " - Shift-click for info...");
  1144.         else
  1145.            showStatus(getAppletInfo());
  1146.      }
  1147.       }
  1148.  
  1149.       return true;
  1150.    }
  1151.  
  1152.    /**
  1153.     * Take care of mouse-enter event to handle show URL (if specified)
  1154.     */
  1155.    public boolean mouseEnter(Event evt, int x, int y) {
  1156.       showUrl();
  1157.       return true;
  1158.    }
  1159.  
  1160.    /**
  1161.     * Display "about" popup frame
  1162.     */
  1163.    void showAboutPopup() {
  1164.       if (!showAbout)
  1165.      return;
  1166.       
  1167.       FunScrollAbout about = new FunScrollAbout(getAppletInfo());
  1168.       
  1169.       about.appendText("\t" + getAppletInfo() + "\n\n");
  1170.       about.appendText(" Copyright (c) 1996, 1997 by " +
  1171.                "Jan Andersson, Torpa Konsult AB.\n\n");
  1172.       about.appendText(" Info, updates and documentation at " +
  1173.                      sourceLocation + "\n\n");
  1174.       
  1175.       about.appendText(" Applet information:\n");
  1176.  
  1177.       about.appendText(" Document base: " + getDocumentBase()+"\n");
  1178.       about.appendText(" Code base: " + getCodeBase()+"\n\n");
  1179.  
  1180.       about.appendText(" Applet parameters:\n");
  1181.       about.appendText("  width = " + getParameter("WIDTH")+"\n");
  1182.       about.appendText("  height = " + getParameter("HEIGHT")+"\n\n");
  1183.  
  1184.       // Display parameter info
  1185.       about.appendText(" Message lines (line<n> parameters):\n");
  1186.       for (int i = 0; i < noOfObjects; i++) {
  1187.      FunScrollAnimate txt = (FunScrollAnimate)
  1188.         animatedObjects.elementAt(i);
  1189.      about.appendText("  line" + i +" = " +
  1190.               txt.getUnparsedTextLine() + "\n");
  1191.      about.appendText("  url" + i +" = ");
  1192.      String urlString = null;
  1193.      if (i < urlStrings.size()) 
  1194.         urlString = (String) urlStrings.elementAt(i);
  1195.      about.appendText(urlString + "\n");
  1196.       }
  1197.       
  1198.       about.appendText("\n Other parameters:\n");
  1199.       String params[][] = getParameterInfo();
  1200.       for (int i = 2; i < params.length; i++) {
  1201.      String parInfo = "  " + params[i][0] + " = " +
  1202.         getParameter(params[i][0]);
  1203.      if (parInfo.length() <= 17)
  1204.         parInfo += "\t";
  1205.      about.appendText(parInfo + "\t [" + params[i][2] + "]\n");
  1206.       }
  1207.       
  1208.       about.show();
  1209.    }
  1210.  
  1211.    /**
  1212.     * Display current url in status line.
  1213.     */
  1214.    void showUrl() {
  1215.       // display current url if specified
  1216.       if (urlStrings != null && currentObjectIndex < urlStrings.size()) {
  1217.      String urlString =
  1218.         (String) urlStrings.elementAt(currentObjectIndex);
  1219.      if (urlString != null) {
  1220.         String tmp = urlString.toUpperCase();
  1221.         int tIndex = tmp.indexOf("TARGET=");
  1222.         if (tIndex > 0) 
  1223.            urlString = urlString.substring(0, tIndex);
  1224.         showStatus(urlString);
  1225.      }
  1226.      else
  1227.         showStatus("");
  1228.       }
  1229.    }
  1230.    
  1231.    /**
  1232.     * Show document as specified in URL string
  1233.     */
  1234.    void showDocument(String urlString) {
  1235.       // check if target option specified in URL string
  1236.       String target = null;
  1237.       String tmp = urlString.toUpperCase();
  1238.       int tIndex = tmp.indexOf("TARGET=");
  1239.       if (tIndex > 0) {
  1240.      target = urlString.substring(tIndex+7);
  1241.      urlString = urlString.substring(0, tIndex);
  1242.      target = target.trim();
  1243.      dbg("target:" + target);
  1244.       }
  1245.  
  1246.       if (target == null)
  1247.      // use target provided as parameter
  1248.      target = urlTarget;
  1249.  
  1250.       // try URL directly
  1251.       URL url = null;
  1252.       try {
  1253.      url = new URL(urlString);
  1254.       }
  1255.       catch (MalformedURLException e) {
  1256.      //System.out.println(e.getMessage());
  1257.      url = null;
  1258.       }
  1259.       if (url == null) {
  1260.      // try to get url in context of current document
  1261.      try {
  1262.         url = new URL(getDocumentBase(), urlString);
  1263.      }
  1264.      catch (MalformedURLException e) {
  1265.         System.out.println("Error. Can't open URL: " + urlString);
  1266.         System.out.println("(" + e.getMessage() + ")");
  1267.         showStatus("Error. Can't open URL: " + urlString);
  1268.         url = null;
  1269.      }
  1270.       }
  1271.       
  1272.       // Load URL, using showDocument()
  1273.       if (url != null) {
  1274.      showStatus("Loading: " + urlString + "...");
  1275.      if (target == null)
  1276.         getAppletContext().showDocument(url);
  1277.      else
  1278.         getAppletContext().showDocument(url, target);
  1279.       }
  1280.    }
  1281.    
  1282.    /**
  1283.     * Simple debug...
  1284.     */
  1285.    static public void dbg(String str) {
  1286.       if (debug) {
  1287.      System.out.println("Debug: " + str);
  1288.      System.out.flush();
  1289.       }
  1290.    }  
  1291.  
  1292.    /**
  1293.     * Get offscreen image
  1294.     */
  1295.    public Image getOffImage()
  1296.    {
  1297.       return offImage;
  1298.    }
  1299.  
  1300.    /**
  1301.     * Get applet frame margin
  1302.     */
  1303.    public int getMargin()
  1304.    {
  1305.       return frameMargin+frameWidth;
  1306.    }
  1307.  
  1308.    /**
  1309.     * Returns a darker version of color.
  1310.     */
  1311.    static Color darker(int r, int g, int b, double factor) {
  1312.       return FunScrollColorSupport.darker(r, g, b, factor);
  1313.    }
  1314.    
  1315.    /**
  1316.     * Returns a darker version of color.
  1317.     */
  1318.    static Color darker(Color c, double factor) {
  1319.       return FunScrollColorSupport.darker(c, factor);
  1320.    }
  1321.    
  1322.    /**
  1323.     * Returns a brighter version of color.
  1324.     */
  1325.    static Color brighter(int r, int g, int b, double factor) {
  1326.       return FunScrollColorSupport.brighter(r, g, b, factor);
  1327.    }
  1328.    
  1329.    /**
  1330.     * Returns a brighter version of color.
  1331.     */
  1332.    static Color brighter(Color c, double factor) {
  1333.       return FunScrollColorSupport.brighter(c, factor);
  1334.    }
  1335.  
  1336.  
  1337. }
  1338.  
  1339.