home *** CD-ROM | disk | FTP | other *** search
/ BUG 15 / BUGCD1998_06.ISO / aplic / jbuilder / jsamples.z / Animator.java < prev    next >
Encoding:
Java Source  |  1997-07-30  |  29.1 KB  |  1,138 lines

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