home *** CD-ROM | disk | FTP | other *** search
/ Ultra Pack / UltraComputing Technology Demos and Tools.iso / java / demo / Animator / Animator.java < prev    next >
Encoding:
Java Source  |  1996-04-26  |  26.1 KB  |  1,072 lines

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