home *** CD-ROM | disk | FTP | other *** search
/ PC World 1998 October / PCWorld_1998-10_cd.bin / software / prehled / inprise / JSAMPLES.Z / Animator.java < prev    next >
Text File  |  1998-05-08  |  30KB  |  1,137 lines

  1. /*
  2.  * @(#)Animator.java    1.9 96/12/06
  3.  *
  4.  * Copyright (c) 1994-1996 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
  7.  * modify and redistribute this software in source and binary code form,
  8.  * provided that i) this copyright notice and license appear on all copies of
  9.  * the software; and ii) Licensee does not utilize the software in a manner
  10.  * which is disparaging to Sun.
  11.  *
  12.  * This software is provided "AS IS," without a warranty of any kind. ALL
  13.  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
  14.  * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
  15.  * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
  16.  * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
  17.  * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
  18.  * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
  19.  * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
  20.  * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
  21.  * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
  22.  * POSSIBILITY OF SUCH DAMAGES.
  23.  *
  24.  * This software is not designed or intended for use in on-line control of
  25.  * aircraft, air traffic, aircraft navigation or aircraft communications; or in
  26.  * the design, construction, operation or maintenance of any nuclear
  27.  * facility. Licensee represents and warrants that it will not use or
  28.  * redistribute the Software for such purposes.
  29.  */
  30.  
  31. import java.awt.*;
  32. import java.applet.Applet;
  33. import java.applet.AudioClip;
  34. import java.util.Vector;
  35. import java.util.Hashtable;
  36. import java.util.Enumeration;
  37. import java.net.URL;
  38. import java.net.MalformedURLException;
  39.  
  40. /**
  41.  * An applet that plays a sequence of images, as a loop or a one-shot.
  42.  * Can have a soundtrack and/or sound effects tied to individual frames.
  43.  * See the <a href="http://java.sun.com/applets/applets/Animator/">Animator
  44.  * home page</a> for details and updates.
  45.  *
  46.  * @author Herb Jellinek
  47.  * @version 1.9, 06/18/96
  48.  */
  49.  
  50. public class Animator extends Applet implements Runnable {
  51.     
  52.     /**
  53.      * The images, in display order (Images).
  54.      */
  55.     Vector images = null;
  56.  
  57.     /**
  58.      * A table that maps images to image names - for error messages.
  59.      */
  60.     Hashtable imageNames = new Hashtable(10);
  61.  
  62.     /**
  63.      * Duration of each image (Integers, in milliseconds).
  64.      */
  65.     Hashtable durations = null;
  66.  
  67.     /**
  68.      * Sound effects for each image (AudioClips).
  69.      */
  70.     Hashtable sounds = null;
  71.  
  72.     /**
  73.      * Position of each image (Points).
  74.      */
  75.     Hashtable positions = null;
  76.  
  77.     /**
  78.      * MediaTracker 'class' ID numbers.
  79.      */
  80.     static final int STARTUP_ID    = 0;
  81.     static final int BACKGROUND_ID = 1;
  82.     static final int ANIMATION_ID  = 2;
  83.  
  84.     /**
  85.      * Start-up image URL, if any.
  86.      */
  87.     URL startUpImageURL = null;
  88.  
  89.     /**
  90.      * Start-up image, if any.
  91.      */
  92.     Image startUpImage = null;
  93.  
  94.     /**
  95.      * Background image URL, if any.
  96.      */
  97.     URL backgroundImageURL = null;
  98.  
  99.     /**
  100.      * Background image, if any.
  101.      */
  102.     Image backgroundImage = null;
  103.  
  104.     /**
  105.      * Background color, if any.
  106.      */
  107.     Color backgroundColor = null;
  108.     
  109.     /**
  110.      * The soundtrack's URL.
  111.      */
  112.     URL soundtrackURL = null;
  113.  
  114.     /**
  115.      * The soundtrack.
  116.      */
  117.     AudioClip soundtrack = null;
  118.  
  119.     /**
  120.      * URL to link to, if any.
  121.      */
  122.     URL hrefURL = null;
  123.  
  124.     /**
  125.      * Frame target for that URL, if any.
  126.      */
  127.     String hrefTarget = null;
  128.  
  129.     /**
  130.      * Our width.
  131.      */
  132.     int appWidth = 0;
  133.  
  134.     /**
  135.      * Our height.
  136.      */
  137.     int appHeight = 0;
  138.  
  139.     /**
  140.      * The directory or URL from which the images are loaded
  141.      */
  142.     URL imageSource = null;
  143.  
  144.     /**
  145.      * The directory or URL from which the sounds are loaded
  146.      */
  147.     URL soundSource = null;
  148.  
  149.     /**
  150.      * The thread animating the images.
  151.      */
  152.     Thread engine = null;
  153.  
  154.     /**
  155.      * The current loop slot - index into 'images.'
  156.      */
  157.     int frameNum;
  158.  
  159.     /**
  160.      * frameNum as an Object - suitable for use as a Hashtable key.
  161.      */
  162.     Integer frameNumKey;
  163.     
  164.     /**
  165.      * The current X position (for painting).
  166.      */
  167.     int xPos = 0;
  168.     
  169.     /**
  170.      * The current Y position (for painting).
  171.      */
  172.     int yPos = 0;
  173.     
  174.     /**
  175.      * The default number of milliseconds to wait between frames.
  176.      */
  177.     public static final int defaultPause = 3900;
  178.     
  179.     /**
  180.      * The global delay between images, which can be overridden by
  181.      * the PAUSE parameter.
  182.      */
  183.     int globalPause = defaultPause;
  184.  
  185.     /**
  186.      * Whether or not the thread has been paused by the user.
  187.      */
  188.     boolean userPause = false;
  189.  
  190.     /**
  191.      * Repeat the animation?  If false, just play it once.
  192.      */
  193.     boolean repeat;
  194.  
  195.     /**
  196.      * The offscreen image, used in double buffering
  197.      */
  198.     Image offScrImage;
  199.  
  200.     /**
  201.      * The offscreen graphics context, used in double buffering
  202.      */
  203.     Graphics offScrGC;
  204.  
  205.     /**
  206.      * The MediaTracker we use to load our images.
  207.      */
  208.     MediaTracker tracker;
  209.     
  210.     /**
  211.      * Can we paint yet?
  212.      */
  213.     boolean loaded = false;
  214.  
  215.     /**
  216.      * Was there an initialization error?
  217.      */
  218.     boolean error = false;
  219.  
  220.     /**
  221.      * What we call an image file in messages.
  222.      */
  223.     static final String imageLabel = "image";
  224.     
  225.     /**
  226.      * What we call a sound file in messages.
  227.      */
  228.     static final String soundLabel = "sound";
  229.     
  230.     /**
  231.      * Print silly debugging info?
  232.      */
  233.     static final boolean debug = false;
  234.  
  235.     /**
  236.      * Where to find the source code and documentation - for informational
  237.      * purposes.
  238.      */
  239.     static final String sourceLocation =
  240.                    "http://java.sun.com/applets/applets/Animator/";
  241.  
  242.     /**
  243.      * Instructions to the user: how to produce the popup.
  244.      */
  245.     static final String userInstructions = "shift-click for errors, info";
  246.  
  247.     /**
  248.      * Applet info.
  249.      */
  250.     public String getAppletInfo() {
  251.     return "Animator v1.9 (06/18/96), by Herb Jellinek";
  252.     }
  253.  
  254.     /**
  255.      * Parameter info.
  256.      */
  257.     public String[][] getParameterInfo() {
  258.     String[][] info = {
  259.         {"imagesource",     "URL",         "a directory"},
  260.         {"startup",     "URL",         "image displayed at start-up"},
  261.         {"backgroundcolor", "int",          "color (24-bit RGB number) displayed as background"},
  262.         {"background",     "URL",         "image displayed as background"},
  263.         {"startimage",     "int",         "index of first image"},
  264.         {"endimage",     "int",         "index of last image"},
  265.         {"namepattern",     "URL",          "generates indexed names"},
  266.         {"images",          "URLs",         "list of image indices"},
  267.         {"href",        "URL",        "page to visit on mouse-click"},
  268.         {"target",        "name",        "frame to put that page in"},
  269.         {"pause",             "int",         "global pause, milliseconds"},
  270.         {"pauses",             "ints",     "individual pauses, milliseconds"},
  271.         {"repeat",             "boolean",     "repeat? true or false"},
  272.         {"positions",    "coordinates",     "path images will follow"},
  273.         {"soundsource",    "URL",         "audio directory"},
  274.         {"soundtrack",    "URL",         "background music"},
  275.         {"sounds",        "URLs",        "list of audio samples"},
  276.     };
  277.     return info;
  278.     }
  279.  
  280.     /**
  281.      * Show a crude "About" box.  Displays credits, errors (if any), and
  282.      * parameter values and documentation.
  283.      */
  284.     void showDescription() {
  285.     DescriptionFrame description = new DescriptionFrame();
  286.         
  287.     description.tell("\t\t"+getAppletInfo()+"\n");
  288.     description.tell("Updates, documentation at "+sourceLocation+"\n\n");
  289.     description.tell("Document base: "+getDocumentBase()+"\n");
  290.     description.tell("Code base: "+getCodeBase()+"\n\n");
  291.     
  292.     Object errors[] = tracker.getErrorsAny();
  293.     if (errors != null) {
  294.         description.tell("Applet image errors:\n");
  295.         for (int i = 0; i < errors.length; i++) {
  296.         if (errors[i] instanceof Image) {
  297.             URL url = (URL)imageNames.get((Image)errors[i]);
  298.             if (url != null) {
  299.             description.tell(" "+url+" not loaded\n");
  300.             }
  301.         }
  302.         }
  303.         description.tell("\n");
  304.     }
  305.     
  306.     if (images == null || images.size() == 0) {
  307.         description.tell("\n** No images loaded **\n\n");
  308.     }
  309.  
  310.     description.tell("Applet parameters:\n");
  311.     description.tell(" width = "+getParameter("WIDTH")+"\n");
  312.     description.tell(" height = "+getParameter("HEIGHT")+"\n");
  313.     
  314.     String params[][] = getParameterInfo();
  315.     for (int i = 0; i < params.length; i++) {
  316.         String name = params[i][0];
  317.         description.tell(" "+name+" = "+getParameter(name)+
  318.                  "\t ["+params[i][2]+"]\n");
  319.     }
  320.     
  321.     description.show();
  322.     }
  323.  
  324.     /**
  325.      * Print silly debugging info to the standard output.
  326.      */
  327.     void dbg(String s) {
  328.     if (debug) {
  329.         System.out.println("> "+s);
  330.     }
  331.     }
  332.  
  333.     /**
  334.      * Local version of getParameter for debugging purposes.
  335.      */
  336.     public String getParam(String key) {
  337.     String result = getParameter(key);
  338.     dbg("getParameter("+key+") = "+result);
  339.     return result;
  340.     }
  341.  
  342.     final int setFrameNum(int newFrameNum) {
  343.     frameNumKey = new Integer(frameNum = newFrameNum);
  344.     return frameNum;
  345.     }
  346.     
  347.     /**
  348.      * Parse the IMAGES parameter.  It looks like
  349.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  350.      *
  351.      * @return a Vector of (URL) image file names.
  352.      */
  353.     Vector parseImages(String attr, String pattern)
  354.     throws MalformedURLException {
  355.     if (pattern == null) {
  356.         pattern = "T%N.gif";
  357.     }
  358.     Vector result = new Vector(10);
  359.     for (int i = 0; i < attr.length(); ) {
  360.         int next = attr.indexOf('|', i);
  361.         if (next == -1) next = attr.length();
  362.         String file = attr.substring(i, next);
  363.         result.addElement(new URL(imageSource, doSubst(pattern, file)));
  364.         i = next + 1;
  365.     }
  366.     return result;
  367.     }
  368.  
  369.     /**
  370.      * Fetch an image and wait for it to come in.  Used to enforce a load
  371.      * order for background and startup images.
  372.      * Places URL and image in imageNames table.
  373.      */
  374.     Image fetchImageAndWait(URL imageURL, int trackerClass) 
  375.     throws InterruptedException {
  376.     Image image = getImage(imageURL);
  377.     tracker.addImage(image, trackerClass);
  378.     imageNames.put(image, imageURL);
  379.     tracker.waitForID(trackerClass);
  380.     return image;
  381.     }
  382.  
  383.     /**
  384.      * Fetch the images named in the argument.  Is restartable.
  385.      *
  386.      * @param images a Vector of URLs
  387.      * @return true if all went well, false otherwise.
  388.      */
  389.     boolean fetchImages(Vector images) {
  390.     if (images == null) {
  391.         return true;
  392.     }
  393.  
  394.     int size = images.size();
  395.     for (int i = 0; i < size; i++) {
  396.         Object o = images.elementAt(i);
  397.         if (o instanceof URL) {
  398.         URL url = (URL)o;
  399.         tellLoadingMsg(url, imageLabel);
  400.         Image im = getImage(url);
  401.         tracker.addImage(im, ANIMATION_ID);
  402.         images.setElementAt(im, i);
  403.         imageNames.put(im, url);
  404.         }
  405.     }
  406.  
  407.     try {
  408.         tracker.waitForID(ANIMATION_ID);
  409.     } catch (InterruptedException e) {}
  410.     if (tracker.isErrorID(ANIMATION_ID)) {
  411.         return false;
  412.     }
  413.  
  414.     return true;
  415.     }
  416.  
  417.     /**
  418.      * Parse the SOUNDS parameter.  It looks like
  419.      * train.au||hello.au||stop.au, etc., where each item refers to a
  420.      * source image.  Empty items mean that the corresponding image
  421.      * has no associated sound.
  422.      *
  423.      * @return a Hashtable of SoundClips keyed to Integer frame numbers.
  424.      */
  425.     Hashtable parseSounds(String attr, Vector images)
  426.     throws MalformedURLException {
  427.     Hashtable result = new Hashtable();
  428.  
  429.     int imageNum = 0;
  430.     int numImages = images.size();
  431.     for (int i = 0; i < attr.length(); ) {
  432.         if (imageNum >= numImages) break;
  433.         
  434.         int next = attr.indexOf('|', i);
  435.         if (next == -1) next = attr.length();
  436.         
  437.         String sound = attr.substring(i, next);
  438.         if (sound.length() != 0) {
  439.         result.put(new Integer(imageNum),
  440.                new URL(soundSource, sound));
  441.         }
  442.         i = next + 1;
  443.         imageNum++;
  444.     }
  445.  
  446.     return result;
  447.     }
  448.  
  449.     /**
  450.      * Fetch the sounds named in the argument.
  451.      * Is restartable.
  452.      *
  453.      * @return URL of the first bogus file we hit, null if OK.
  454.      */
  455.     URL fetchSounds(Hashtable sounds) {
  456.     for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
  457.         Integer num = (Integer)e.nextElement();
  458.         Object o = sounds.get(num);
  459.         if (o instanceof URL) {
  460.         URL file = (URL)o;
  461.         tellLoadingMsg(file, soundLabel);
  462.         try {
  463.             sounds.put(num, getAudioClip(file));
  464.         } catch (Exception ex) {
  465.             return file;
  466.         }
  467.         }
  468.     }
  469.     return null;
  470.     }
  471.  
  472.     /**
  473.      * Parse the PAUSES parameter.  It looks like
  474.      * 1000|500|||750, etc., where each item corresponds to a
  475.      * source image.  Empty items mean that the corresponding image
  476.      * has no special duration, and should use the global one.
  477.      *
  478.      * @return a Hashtable of Integer pauses keyed to Integer
  479.      * frame numbers.
  480.      */
  481.     Hashtable parseDurations(String attr, Vector images) {
  482.     Hashtable result = new Hashtable();
  483.  
  484.     int imageNum = 0;
  485.     int numImages = images.size();
  486.     for (int i = 0; i < attr.length(); ) {
  487.         if (imageNum >= numImages) break;
  488.         
  489.         int next = attr.indexOf('|', i);
  490.         if (next == -1) next = attr.length();
  491.  
  492.         if (i != next) {
  493.         int duration = Integer.parseInt(attr.substring(i, next));
  494.         result.put(new Integer(imageNum), new Integer(duration));
  495.         } else {
  496.         result.put(new Integer(imageNum),
  497.                new Integer(globalPause));
  498.         }
  499.         i = next + 1;
  500.         imageNum++;
  501.     }
  502.  
  503.     return result;
  504.     }
  505.  
  506.     /**
  507.      * Parse a String of form xxx@yyy and return a Point.
  508.      */
  509.     Point parsePoint(String s) throws ParseException {
  510.     int atPos = s.indexOf('@');
  511.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  512.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  513.              Integer.parseInt(s.substring(atPos + 1)));
  514.     }
  515.  
  516.  
  517.     /**
  518.      * Parse the POSITIONS parameter.  It looks like
  519.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  520.      * corresponding to a source image.  Empty items mean that the
  521.      * corresponding image has the same position as the preceding one.
  522.      *
  523.      * @return a Hashtable of Points keyed to Integer frame numbers.
  524.      */
  525.     Hashtable parsePositions(String param, Vector images)
  526.     throws ParseException {
  527.     Hashtable result = new Hashtable();
  528.  
  529.     int imageNum = 0;
  530.     int numImages = images.size();
  531.     for (int i = 0; i < param.length(); ) {
  532.         if (imageNum >= numImages) break;
  533.         
  534.         int next = param.indexOf('|', i);
  535.         if (next == -1) next = param.length();
  536.  
  537.         if (i != next) {
  538.         result.put(new Integer(imageNum),
  539.                parsePoint(param.substring(i, next)));
  540.         }
  541.         i = next + 1;
  542.         imageNum++;
  543.     }
  544.  
  545.     return result;
  546.     }
  547.     
  548.     /**
  549.      * Get the dimensions of an image.
  550.      * @return the image's dimensions.
  551.      */
  552.     Dimension getImageDimensions(Image im) {
  553.     return new Dimension(im.getWidth(null), im.getHeight(null));
  554.     }
  555.  
  556.     /**
  557.      * Substitute an integer some number of times in a string, subject to
  558.      * parameter strings embedded in the string.
  559.      * Parameter strings:
  560.      *   %N - substitute the integer as is, with no padding.
  561.      *   %<digit>, for example %5 - substitute the integer left-padded with
  562.      *        zeros to <digits> digits wide.
  563.      *   %% - substitute a '%' here.
  564.      * @param inStr the String to substitute within
  565.      * @param theInt the int to substitute, as a String.
  566.      */
  567.     String doSubst(String inStr, String theInt) {
  568.     String padStr = "0000000000";
  569.     int length = inStr.length();
  570.     StringBuffer result = new StringBuffer(length);
  571.     
  572.     for (int i = 0; i < length;) {
  573.         char ch = inStr.charAt(i);
  574.         if (ch == '%') {
  575.         i++;
  576.         if (i == length) {
  577.             result.append(ch);
  578.         } else {
  579.             ch = inStr.charAt(i);
  580.             if (ch == 'N' || ch == 'n') {
  581.             // just stick in the number, unmolested
  582.             result.append(theInt);
  583.             i++;
  584.             } else {
  585.             int pad;
  586.             if ((pad = Character.digit(ch, 10)) != -1) {
  587.                 // we've got a width value
  588.                 String numStr = theInt;
  589.                 String scr = padStr+numStr;
  590.                 result.append(scr.substring(scr.length() - pad));
  591.                 i++;
  592.             } else {
  593.                 result.append(ch);
  594.                 i++;
  595.             }
  596.             }
  597.         }
  598.         } else {
  599.         result.append(ch);
  600.         i++;
  601.         }
  602.     }
  603.     return result.toString();
  604.     }    
  605.  
  606.     /**
  607.      * Stuff a range of image names into a Vector.
  608.      *
  609.      * @return a Vector of image URLs.
  610.      */
  611.     Vector prepareImageRange(int startImage, int endImage, String pattern)
  612.     throws MalformedURLException {
  613.     Vector result = new Vector(Math.abs(endImage - startImage) + 1);
  614.     if (pattern == null) {
  615.         pattern = "T%N.gif";
  616.     }
  617.     if (startImage > endImage) {
  618.         for (int i = startImage; i >= endImage; i--) {
  619.         result.addElement(new URL(imageSource, doSubst(pattern, i+"")));
  620.         }
  621.     } else {
  622.         for (int i = startImage; i <= endImage; i++) {
  623.         result.addElement(new URL(imageSource, doSubst(pattern, i+"")));
  624.         }
  625.     }
  626.  
  627.     return result;
  628.     }
  629.  
  630.     
  631.     /**
  632.      * Initialize the applet.  Get parameters.
  633.      */
  634.     public void init() {
  635.  
  636.  
  637.     tracker = new MediaTracker(this);
  638.  
  639.     appWidth = size().width;
  640.     appHeight = size().height;
  641.  
  642.     try {
  643.         String param = getParam("IMAGESOURCE");    
  644.         imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
  645.     
  646.         String href = getParam("HREF");
  647.         if (href != null) {
  648.         try {
  649.             hrefURL = new URL(getDocumentBase(), href);
  650.         } catch (MalformedURLException e) {
  651.             showParseError(e);
  652.         }
  653.         }
  654.  
  655.         hrefTarget = getParam("TARGET");
  656.         if (hrefTarget == null) {
  657.         hrefTarget = "_top";
  658.         }
  659.  
  660.         param = getParam("PAUSE");
  661.         globalPause =
  662.         (param != null) ? Integer.parseInt(param) : defaultPause;
  663.  
  664.         param = getParam("REPEAT");
  665.         repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
  666.                            param.equalsIgnoreCase("true"));
  667.  
  668.         int startImage = 1;
  669.         int endImage = 1;
  670.         param = getParam("ENDIMAGE");
  671.         if (param != null) {
  672.         endImage = Integer.parseInt(param);
  673.         param = getParam("STARTIMAGE");
  674.         if (param != null) {
  675.             startImage = Integer.parseInt(param);
  676.         }
  677.         param = getParam("NAMEPATTERN");
  678.         images = prepareImageRange(startImage, endImage, param);
  679.         } else {
  680.         param = getParam("STARTIMAGE");
  681.         if (param != null) {
  682.             startImage = Integer.parseInt(param);
  683.             param = getParam("NAMEPATTERN");
  684.             images = prepareImageRange(startImage, endImage, param);
  685.         } else {
  686.             param = getParam("IMAGES");
  687.             if (param == null) {
  688.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  689.                    "specified.");
  690.             error = true;
  691.             return;
  692.             } else {
  693.             images = parseImages(param, getParam("NAMEPATTERN"));
  694.             }
  695.         }
  696.         }
  697.  
  698.         param = getParam("BACKGROUND");
  699.         if (param != null) {
  700.         backgroundImageURL = new URL(imageSource, param);
  701.         }
  702.  
  703.         param = getParam("BACKGROUNDCOLOR");
  704.         if (param != null) {
  705.         backgroundColor = decodeColor(param);
  706.         }
  707.  
  708.         param = getParam("STARTUP");
  709.         if (param != null) {
  710.         startUpImageURL = new URL(imageSource, param);
  711.         }
  712.  
  713.         param = getParam("SOUNDSOURCE");
  714.         soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
  715.     
  716.         param = getParam("SOUNDS");
  717.         if (param != null) {
  718.         sounds = parseSounds(param, images);
  719.         }
  720.  
  721.         param = getParam("PAUSES");
  722.         if (param != null) {
  723.         durations = parseDurations(param, images);
  724.         }
  725.  
  726.         param = getParam("POSITIONS");
  727.         if (param != null) {
  728.         positions = parsePositions(param, images);
  729.         }
  730.  
  731.         param = getParam("SOUNDTRACK");
  732.         if (param != null) {
  733.         soundtrackURL = new URL(soundSource, param);
  734.         }
  735.     } catch (MalformedURLException e) {
  736.         showParseError(e);
  737.     } catch (ParseException e) {
  738.         showParseError(e);
  739.     }
  740.  
  741.     setFrameNum(0);
  742.     }
  743.  
  744.     Color decodeColor(String s) {
  745.     int val = 0;
  746.     try {
  747.         if (s.startsWith("0x")) {
  748.         val = Integer.parseInt(s.substring(2), 16);
  749.         } else if (s.startsWith("#")) {
  750.         val = Integer.parseInt(s.substring(1), 16);
  751.         } else if (s.startsWith("0") && s.length() > 1) {
  752.         val = Integer.parseInt(s.substring(1), 8);
  753.         } else {
  754.         val = Integer.parseInt(s, 10);
  755.         }
  756.         return new Color(val);
  757.     } catch (NumberFormatException e) {
  758.         return null;
  759.     }
  760.     }
  761.  
  762.     void tellLoadingMsg(String file, String fileType) {
  763.     showStatus("Animator: loading "+fileType+" "+file);
  764.     }
  765.  
  766.     void tellLoadingMsg(URL url, String fileType) {
  767.     tellLoadingMsg(url.toExternalForm(), fileType);
  768.     }
  769.  
  770.     void clearLoadingMessage() {
  771.     showStatus("");
  772.     }
  773.     
  774.     void loadError(String fileName, String fileType) {
  775.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  776.         fileName;
  777.     showStatus(errorMsg);
  778.     System.err.println(errorMsg);
  779.     error = true;
  780.     repaint();
  781.     }
  782.  
  783.     void loadError(URL badURL, String fileType) {
  784.     loadError(badURL.toExternalForm(), fileType);
  785.     }
  786.  
  787.     void loadErrors(Object errors[], String fileType) {
  788.     for (int i = 0; i < errors.length; i++) {
  789.         if (errors[i] instanceof Image) {
  790.         URL url = (URL)imageNames.get((Image)errors[i]);
  791.         if (url != null) {
  792.             loadError(url, fileType);
  793.         }
  794.         }
  795.     }
  796.     }
  797.  
  798.     void showParseError(Exception e) {
  799.     String errorMsg = "Animator: Parse error: "+e;
  800.     showStatus(errorMsg);
  801.     System.err.println(errorMsg);
  802.     error = true;
  803.     repaint();
  804.     }
  805.  
  806.     void startPlaying() {
  807.     if (soundtrack != null) {
  808.         soundtrack.loop();
  809.     }
  810.     }
  811.  
  812.     void stopPlaying() {
  813.     if (soundtrack != null) {
  814.         soundtrack.stop();
  815.     }
  816.     }
  817.  
  818.     /**
  819.      * Run the animation. This method is called by class Thread.
  820.      * @see java.lang.Thread
  821.      */
  822.     public void run() {
  823.     Thread me = Thread.currentThread();
  824.     URL badURL;
  825.     
  826.     me.setPriority(Thread.MIN_PRIORITY);
  827.  
  828.     if (! loaded) {
  829.         try {
  830.         // ... to do a bunch of loading.
  831.         if (startUpImageURL != null) {
  832.             tellLoadingMsg(startUpImageURL, imageLabel);
  833.             startUpImage = fetchImageAndWait(startUpImageURL,
  834.                              STARTUP_ID);
  835.             if (tracker.isErrorID(STARTUP_ID)) {
  836.             loadError(startUpImageURL, "start-up image");
  837.             }
  838.             repaint();
  839.         }
  840.         
  841.         if (backgroundImageURL != null) {
  842.             tellLoadingMsg(backgroundImageURL, imageLabel);
  843.             backgroundImage = fetchImageAndWait(backgroundImageURL, 
  844.                             BACKGROUND_ID);
  845.             if (tracker.isErrorID(BACKGROUND_ID)) {
  846.             loadError(backgroundImageURL, "background image");
  847.             }
  848.             repaint();
  849.         }
  850.  
  851.         // Fetch the animation frames
  852.         if (!fetchImages(images)) {
  853.             // get images in error, tell user about first of them
  854.             Object errors[] = tracker.getErrorsAny();
  855.             loadErrors(errors, imageLabel);
  856.             return;
  857.         }
  858.  
  859.         if (soundtrackURL != null && soundtrack == null) {
  860.             tellLoadingMsg(soundtrackURL, imageLabel);
  861.             soundtrack = getAudioClip(soundtrackURL);
  862.             if (soundtrack == null) {
  863.             loadError(soundtrackURL, "soundtrack");
  864.             return;
  865.             }
  866.         }
  867.  
  868.         if (sounds != null) {
  869.             badURL = fetchSounds(sounds);
  870.             if (badURL != null) {
  871.             loadError(badURL, soundLabel);
  872.             return;
  873.             }
  874.         }
  875.  
  876.         clearLoadingMessage();
  877.  
  878.         offScrImage = createImage(appWidth, appHeight);
  879.         offScrGC = offScrImage.getGraphics();
  880.         offScrGC.setColor(Color.lightGray);
  881.  
  882.         loaded = true;
  883.         error = false;
  884.         } catch (Exception e) {
  885.         error = true;
  886.         e.printStackTrace();
  887.         }
  888.     }
  889.  
  890.     if (userPause) {
  891.         return;
  892.     }
  893.  
  894.     if (repeat || frameNum < images.size()) {
  895.         startPlaying();
  896.     }
  897.  
  898.     try {
  899.         if (images != null && images.size() > 1) {
  900.         while (appWidth > 0 && appHeight > 0 && engine == me) {
  901.             if (frameNum >= images.size()) {
  902.             if (!repeat) {
  903.                 return;
  904.             }
  905.             setFrameNum(0);
  906.             }
  907.             repaint();
  908.  
  909.             if (sounds != null) {
  910.             AudioClip clip =
  911.                 (AudioClip)sounds.get(frameNumKey);
  912.             if (clip != null) {
  913.                 clip.play();
  914.             }
  915.             }
  916.  
  917.             try {
  918.             Integer pause = null;
  919.             if (durations != null) {
  920.                 pause = (Integer)durations.get(frameNumKey);
  921.             }
  922.             if (pause == null) {
  923.                 Thread.sleep(globalPause);
  924.             } else {
  925.                 Thread.sleep(pause.intValue());
  926.             }
  927.             } catch (InterruptedException e) {
  928.             // Should we do anything?
  929.             }
  930.             setFrameNum(frameNum+1);
  931.         }
  932.         }
  933.     } finally {
  934.         stopPlaying();
  935.     }
  936.     }
  937.  
  938.     /**
  939.      * No need to clear anything; just paint.
  940.      */
  941.     public void update(Graphics g) {
  942.     paint(g);
  943.     }
  944.  
  945.     /**
  946.      * Paint the current frame.
  947.      */
  948.     public void paint(Graphics g) {
  949.     if (error || !loaded) {
  950.         if (startUpImage != null) {
  951.         if (tracker.checkID(STARTUP_ID)) {
  952.             if (backgroundColor != null) {
  953.             g.setColor(backgroundColor);
  954.             g.fillRect(0, 0, appWidth, appHeight);
  955.             }
  956.             g.drawImage(startUpImage, 0, 0, this);
  957.         }
  958.         } else {
  959.         if (backgroundImage != null) {
  960.             if (tracker.checkID(BACKGROUND_ID)) {
  961.             g.drawImage(backgroundImage, 0, 0, this);
  962.             }
  963.         } else {
  964.             g.clearRect(0, 0, appWidth, appHeight);
  965.         }
  966.         }
  967.     } else {
  968.         if ((images != null) && (images.size() > 0) &&
  969.         tracker.checkID(ANIMATION_ID)) {
  970.         if (frameNum < images.size()) {
  971.             if (backgroundImage == null) {
  972.             offScrGC.clearRect(0, 0, appWidth, appHeight);
  973.             } else {
  974.             offScrGC.drawImage(backgroundImage, 0, 0, this);
  975.             }
  976.  
  977.             Image image = (Image)images.elementAt(frameNum);
  978.             
  979.             Point pos = null;
  980.             if (positions != null) {
  981.             pos = (Point)positions.get(frameNumKey);
  982.             }
  983.             if (pos != null) {
  984.             xPos = pos.x;
  985.             yPos = pos.y;
  986.             }
  987.             if (backgroundColor != null) {
  988.             offScrGC.setColor(backgroundColor);
  989.             offScrGC.fillRect(0, 0, appWidth, appHeight);
  990.             offScrGC.drawImage(image, xPos, yPos, backgroundColor,
  991.                        this);
  992.             } else {
  993.             offScrGC.drawImage(image, xPos, yPos, this);
  994.             }
  995.             g.drawImage(offScrImage, 0, 0, this);
  996.         } else {
  997.             // no more animation, but need to draw something
  998.             dbg("No more animation; drawing last image.");
  999.             if (backgroundImage == null) {
  1000.             g.fillRect(0, 0, appWidth, appHeight);
  1001.             } else {
  1002.             g.drawImage(backgroundImage, 0, 0, this);
  1003.             }
  1004.             g.drawImage((Image)images.lastElement(), 0, 0, this);
  1005.         }
  1006.         }
  1007.     }
  1008.     }
  1009.  
  1010.     /**
  1011.      * Start the applet by forking an animation thread.
  1012.      */
  1013.     public void start() {
  1014.     if (engine == null) {
  1015.         engine = new Thread(this);
  1016.         engine.start();
  1017.     }
  1018.     showStatus(getAppletInfo());
  1019.     }
  1020.  
  1021.     /**
  1022.      * Stop the insanity, um, applet.
  1023.      */
  1024.     public void stop() {
  1025.     if (engine != null && engine.isAlive()) {
  1026.         engine.stop();
  1027.     }
  1028.     engine = null;
  1029.     }
  1030.  
  1031.     /**
  1032.      * Pause the thread when the user clicks the mouse in the applet.
  1033.      * If the thread has stopped (as in a non-repeat performance),
  1034.      * restart it.
  1035.      */
  1036.     public boolean handleEvent(Event evt) {
  1037.     switch (evt.id) {
  1038.     case Event.MOUSE_DOWN:
  1039.         if ((evt.modifiers & Event.SHIFT_MASK) != 0) {
  1040.         showDescription();
  1041.         return true;
  1042.         } else if (hrefURL != null) {
  1043.         // let mouse-up handle going to the new page
  1044.         return true;
  1045.         } else if (loaded) {
  1046.         if (engine != null && engine.isAlive()) {
  1047.             if (userPause) {
  1048.             engine.resume();
  1049.             startPlaying();
  1050.             } else {
  1051.             engine.suspend();
  1052.             stopPlaying();
  1053.             }
  1054.             userPause = !userPause;
  1055.         } else {
  1056.             userPause = false;
  1057.             setFrameNum(0);
  1058.             engine = new Thread(this);
  1059.             engine.start();
  1060.         }
  1061.         }
  1062.         return true;
  1063.     case Event.MOUSE_UP:
  1064.         if (hrefURL != null && ((evt.modifiers & Event.SHIFT_MASK) == 0)) {
  1065.         getAppletContext().showDocument(hrefURL, hrefTarget);
  1066.         }
  1067.         return true;
  1068.     case Event.MOUSE_ENTER:
  1069.         showStatus(getAppletInfo()+" -- "+userInstructions);
  1070.         return true;
  1071.     case Event.MOUSE_EXIT:
  1072.         showStatus("");
  1073.         return true;
  1074.     case Event.KEY_ACTION:
  1075.     case Event.KEY_RELEASE:
  1076.     case Event.KEY_ACTION_RELEASE:
  1077.         dbg("Got event "+evt);
  1078.         return true;
  1079.     default:
  1080.         return super.handleEvent(evt);
  1081.     }
  1082.     }
  1083.     
  1084. }
  1085.  
  1086.  
  1087. /**
  1088.  * ParseException: signals a parameter parsing problem.
  1089.  */
  1090. class ParseException extends Exception {
  1091.     ParseException(String s) {
  1092.     super(s);
  1093.     }
  1094. }
  1095.  
  1096.  
  1097. /**
  1098.  * DescriptionFrame: implements a pop-up "About" box.
  1099.  */
  1100. class DescriptionFrame extends Frame {
  1101.  
  1102.     static final int rows = 27;
  1103.     static final int cols = 70;
  1104.     
  1105.     TextArea info;
  1106.     Button cancel;
  1107.  
  1108.     DescriptionFrame() {
  1109.     super("Animator v1.9");
  1110.     add("Center", info = new TextArea(rows, cols));
  1111.     info.setEditable(false);
  1112.     info.setBackground(Color.white);
  1113.     Panel buttons = new Panel();
  1114.     add("South", buttons);
  1115.     buttons.add(cancel = new Button("Cancel"));
  1116.     
  1117.     pack();
  1118.     }
  1119.  
  1120.     public void show() {
  1121.     info.select(0,0);
  1122.     super.show();
  1123.     }
  1124.  
  1125.     void tell(String s) {
  1126.     info.appendText(s);
  1127.     }
  1128.  
  1129.     public boolean action(Event e, Object arg) {
  1130.     if (e.target == cancel) {
  1131.         hide();
  1132.         return true;
  1133.     }
  1134.     return false;
  1135.     }
  1136. }
  1137.