home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1996 December / PCWKCD1296.iso / vjplusb / msdev / samples / sun / animator / animator.java < prev    next >
Text File  |  1996-07-10  |  22KB  |  884 lines

  1. /*
  2.  * %W% %E% Herb Jellinek
  3.  *
  4.  * Copyright (c) 1994-1995 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Permission to use, copy, modify, and distribute this software
  7.  * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
  8.  * without fee is hereby granted. 
  9.  * Please refer to the file http://java.sun.com/copy_trademarks.html
  10.  * for further important copyright and trademark information and to
  11.  * http://java.sun.com/licensing.html for further important licensing
  12.  * information for the Java (tm) Technology.
  13.  * 
  14.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  15.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  16.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  18.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  19.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  20.  * 
  21.  * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
  22.  * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
  23.  * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
  24.  * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
  25.  * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
  26.  * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
  27.  * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  SUN
  28.  * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
  29.  * HIGH RISK ACTIVITIES.
  30.  */
  31.  
  32. import java.io.InputStream;
  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.io.File;
  41. import java.net.URL;
  42. import java.net.MalformedURLException;
  43.  
  44. /**
  45.  * An applet that plays a sequence of images, as a loop or a one-shot.
  46.  * Can have a soundtrack and/or sound effects tied to individual frames.
  47.  *
  48.  * @author Herb Jellinek
  49.  * @version %I%, %G%
  50.  */
  51.  
  52. public class animator extends Applet implements Runnable {
  53.     
  54.     /**
  55.      * The images, in display order (Images).
  56.      */
  57.     Vector images = null;
  58.  
  59.     /**
  60.      * Duration of each image (Integers, in milliseconds).
  61.      */
  62.     Hashtable durations = null;
  63.  
  64.     /**
  65.      * Sound effects for each image (AudioClips).
  66.      */
  67.     Hashtable sounds = null;
  68.  
  69.     /**
  70.      * Position of each image (Points).
  71.      */
  72.     Hashtable positions = null;
  73.  
  74.     /**
  75.      * Background image URL, if any.
  76.      */
  77.     URL backgroundImageURL = null;
  78.  
  79.     /**
  80.      * Background image, if any.
  81.      */
  82.     Image backgroundImage = null;
  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.      * The soundtrack's URL.
  96.      */
  97.     URL soundtrackURL = null;
  98.  
  99.     /**
  100.      * The soundtrack.
  101.      */
  102.     AudioClip soundtrack;
  103.  
  104.     /**
  105.      * Largest width.
  106.      */
  107.     int maxWidth = 0;
  108.  
  109.     /**
  110.      * Largest height.
  111.      */
  112.     int maxHeight = 0;
  113.  
  114.     /**
  115.      * Was there a problem loading the current image?
  116.      */
  117.     boolean imageLoadError = false;
  118.  
  119.     /**
  120.      * The directory or URL from which the images are loaded
  121.      */
  122.     URL imageSource = null;
  123.  
  124.     /**
  125.      * The directory or URL from which the sounds are loaded
  126.      */
  127.     URL soundSource = null;
  128.  
  129.     /**
  130.      * The thread animating the images.
  131.      */
  132.     Thread engine = null;
  133.  
  134.     /**
  135.      * The current loop slot - index into 'images.'
  136.      */
  137.     int frameNum;
  138.  
  139.     /**
  140.      * frameNum as an Object - suitable for use as a Hashtable key.
  141.      */
  142.     Integer frameNumKey;
  143.     
  144.     /**
  145.      * The current X position (for painting).
  146.      */
  147.     int xPos = 0;
  148.     
  149.     /**
  150.      * The current Y position (for painting).
  151.      */
  152.     int yPos = 0;
  153.     
  154.     /**
  155.      * The default number of milliseconds to wait between frames.
  156.      */
  157.     public static final int defaultPause = 3900;
  158.     
  159.     /**
  160.      * The global delay between images, which can be overridden by
  161.      * the PAUSE parameter.
  162.      */
  163.     int globalPause = defaultPause;
  164.  
  165.     /**
  166.      * Whether or not the thread has been paused by the user.
  167.      */
  168.     boolean userPause = false;
  169.  
  170.     /**
  171.      * Repeat the animation?  If false, just play it once.
  172.      */
  173.     boolean repeat;
  174.  
  175.     /**
  176.      * Load all images before starting display, or do it asynchronously?
  177.      */
  178.     boolean loadFirst;
  179.     
  180.     /**
  181.      * The offscreen image, used in double buffering
  182.      */
  183.     Image offScrImage;
  184.  
  185.     /**
  186.      * The offscreen graphics context, used in double buffering
  187.      */
  188.     Graphics offScrGC;
  189.  
  190.     /**
  191.      * Can we paint yet?
  192.      */
  193.     boolean loaded = false;
  194.  
  195.     /**
  196.      * Was there an initialization error?
  197.      */
  198.     boolean error = false;
  199.  
  200.     /**
  201.      * What we call an image file in messages.
  202.      */
  203.     final static String imageLabel = "image";
  204.     
  205.     /**
  206.      * What we call a sound file in messages.
  207.      */
  208.     final static String soundLabel = "sound";
  209.     
  210.     /**
  211.      * Print silly debugging info?
  212.      */
  213.     boolean debug = false;
  214.  
  215.     /**
  216.      * Info.
  217.      */
  218.     public String getAppletInfo() {
  219.     return "Animator by Herb Jellinek";
  220.     }
  221.  
  222.     /**
  223.      * Parameter Info
  224.      */
  225.     public String[][] getParameterInfo() {
  226.     String[][] info = {
  227.         {"imagesource",     "url",         "a directory"},
  228.         {"startup",     "url",         "displayed at startup"},
  229.         {"background",     "url",         "displayed as background"},
  230.         {"startimage",     "int",         "start index"},
  231.         {"endimage",     "int",         "end index"},
  232.         {"pause",             "int",         "milliseconds"},
  233.         {"pauses",             "ints",     "milliseconds"},
  234.         {"repeat",             "boolean",     "repeat or not"},
  235.         {"positions",    "coordinates",     "path"},
  236.         {"soundsource",    "url",         "audio directory"},
  237.         {"soundtrack",    "url",         "background music"},
  238.         {"sounds",        "urls",        "audio samples"},
  239.     };
  240.     return info;
  241.     }
  242.  
  243.     /**
  244.      * Print silly debugging info.
  245.      */
  246.     void dbg(String s) {
  247.     if (debug) {
  248.         System.out.println(s);
  249.     }
  250.     }
  251.  
  252.     final int setFrameNum(int newFrameNum) {
  253.     frameNumKey = new Integer(frameNum = newFrameNum);
  254.     return frameNum;
  255.     }
  256.     
  257.     public synchronized boolean imageUpdate(Image img, int infoFlags,
  258.                             int x, int y,
  259.                         int width, int height) {
  260.         if ((infoFlags & ERROR) != 0) {
  261.         imageLoadError = true;
  262.     }
  263.  
  264.     notifyAll();
  265.     return true;
  266.     }
  267.  
  268.     void updateMaxDims(Dimension dim) {
  269.     maxWidth = Math.max(dim.width, maxWidth);
  270.     maxHeight = Math.max(dim.height, maxHeight);
  271.     }
  272.  
  273.     /**
  274.      * Parse the IMAGES parameter.  It looks like
  275.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  276.      *
  277.      * Returns a Vector of image file names.
  278.      */
  279.     Vector parseImages(String attr) {
  280.     Vector result = new Vector(10);
  281.     for (int i = 0; i < attr.length(); ) {
  282.         int next = attr.indexOf('|', i);
  283.         if (next == -1) next = attr.length();
  284.         String file = attr.substring(i, next);
  285.         result.addElement(file);
  286.         i = next + 1;
  287.     }
  288.     return result;
  289.     }
  290.  
  291.     /**
  292.      * Fetch the images named in the argument, updating 
  293.      * maxWidth and maxHeight as we go.
  294.      * Is restartable.
  295.      *
  296.      * @return URL of the first bogus file we hit, null if OK.
  297.      */
  298.     URL fetchImages(Vector images) {
  299.     for (int i = 0; i < images.size(); i++) {
  300.         Object o = images.elementAt(i);
  301.         if (o instanceof URL) {
  302.         URL url = (URL)o;
  303.         tellLoadingMsg(url, imageLabel);
  304.         Image im = getImage(url);
  305.         try {
  306.             updateMaxDims(getImageDimensions(im));
  307.         } catch (Exception e) {
  308.             return url;
  309.         }
  310.         images.setElementAt(im, i);
  311.         }
  312.     }
  313.     return null;
  314.     }
  315.  
  316.     /**
  317.      * Parse the SOUNDS parameter.  It looks like
  318.      * train.au||hello.au||stop.au, etc., where each item refers to a
  319.      * source image.  Empty items mean that the corresponding image
  320.      * has no associated sound.
  321.      *
  322.      * @return a Hashtable of SoundClips keyed to Integer frame numbers.
  323.      */
  324.     Hashtable parseSounds(String attr, Vector images)
  325.     throws MalformedURLException {
  326.     Hashtable result = new Hashtable();
  327.  
  328.     int imageNum = 0;
  329.     int numImages = images.size();
  330.     for (int i = 0; i < attr.length(); ) {
  331.         if (imageNum >= numImages) break;
  332.         
  333.         int next = attr.indexOf('|', i);
  334.         if (next == -1) next = attr.length();
  335.         
  336.         String sound = attr.substring(i, next);
  337.         if (sound.length() != 0) {
  338.         result.put(new Integer(imageNum),
  339.                new URL(soundSource, sound));
  340.         }
  341.         i = next + 1;
  342.         imageNum++;
  343.     }
  344.  
  345.     return result;
  346.     }
  347.  
  348.     /**
  349.      * Fetch the sounds named in the argument.
  350.      * Is restartable.
  351.      *
  352.      * @return URL of the first bogus file we hit, null if OK.
  353.      */
  354.     URL fetchSounds(Hashtable sounds) {
  355.     for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
  356.         Integer num = (Integer)e.nextElement();
  357.         Object o = sounds.get(num);
  358.         if (o instanceof URL) {
  359.         URL file = (URL)o;
  360.         tellLoadingMsg(file, soundLabel);
  361.         try {
  362.             sounds.put(num, getAudioClip(file));
  363.         } catch (Exception ex) {
  364.             return file;
  365.         }
  366.         }
  367.     }
  368.     return null;
  369.     }
  370.  
  371.     /**
  372.      * Parse the PAUSES parameter.  It looks like
  373.      * 1000|500|||750, etc., where each item corresponds to a
  374.      * source image.  Empty items mean that the corresponding image
  375.      * has no special duration, and should use the global one.
  376.      *
  377.      * @return a Hashtable of Integer pauses keyed to Integer
  378.      * frame numbers.
  379.      */
  380.     Hashtable parseDurations(String attr, Vector images) {
  381.     Hashtable result = new Hashtable();
  382.  
  383.     int imageNum = 0;
  384.     int numImages = images.size();
  385.     for (int i = 0; i < attr.length(); ) {
  386.         if (imageNum >= numImages) break;
  387.         
  388.         int next = attr.indexOf('|', i);
  389.         if (next == -1) next = attr.length();
  390.  
  391.         if (i != next - 1) {
  392.         int duration = Integer.parseInt(attr.substring(i, next));
  393.         result.put(new Integer(imageNum), new Integer(duration));
  394.         } else {
  395.         result.put(new Integer(imageNum),
  396.                new Integer(globalPause));
  397.         }
  398.         i = next + 1;
  399.         imageNum++;
  400.     }
  401.  
  402.     return result;
  403.     }
  404.  
  405.     /**
  406.      * Parse a String of form xxx@yyy and return a Point.
  407.      */
  408.     Point parsePoint(String s) throws ParseException {
  409.     int atPos = s.indexOf('@');
  410.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  411.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  412.              Integer.parseInt(s.substring(atPos + 1)));
  413.     }
  414.  
  415.  
  416.     /**
  417.      * Parse the POSITIONS parameter.  It looks like
  418.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  419.      * corresponding to a source image.  Empty items mean that the
  420.      * corresponding image has the same position as the preceding one.
  421.      *
  422.      * @return a Hashtable of Points keyed to Integer frame numbers.
  423.      */
  424.     Hashtable parsePositions(String param, Vector images)
  425.     throws ParseException {
  426.     Hashtable result = new Hashtable();
  427.  
  428.     int imageNum = 0;
  429.     int numImages = images.size();
  430.     for (int i = 0; i < param.length(); ) {
  431.         if (imageNum >= numImages) break;
  432.         
  433.         int next = param.indexOf('|', i);
  434.         if (next == -1) next = param.length();
  435.  
  436.         if (i != next) {
  437.         result.put(new Integer(imageNum),
  438.                parsePoint(param.substring(i, next)));
  439.         }
  440.         i = next + 1;
  441.         imageNum++;
  442.     }
  443.  
  444.     return result;
  445.     }
  446.     
  447.     /**
  448.      * Get the dimensions of an image.
  449.      * @return the image's dimensions.
  450.      */
  451.     synchronized Dimension getImageDimensions(Image im)
  452.     throws ImageNotFoundException {
  453.     // Get the width of the image.
  454.     int width;
  455.     int height;
  456.     
  457.     while ((width = im.getWidth(this)) < 0) {
  458.         try {
  459.         wait();
  460.         } catch (InterruptedException e) { }
  461.         if (imageLoadError) {
  462.         throw new ImageNotFoundException(im.getSource());
  463.         }
  464.     }
  465.     
  466.     // Get the height of the image.
  467.     while ((height = im.getHeight(this)) < 0) {
  468.         try {
  469.         wait();
  470.         } catch (InterruptedException e) { }
  471.         if (imageLoadError) {
  472.         throw new ImageNotFoundException(im.getSource());
  473.         }
  474.     }
  475.  
  476.     return new Dimension(width, height);
  477.     }
  478.  
  479.     /**
  480.      * Stuff a range of image names into a Vector.
  481.      * @return a Vector of image URLs.
  482.      */
  483.     Vector prepareImageRange(int startImage, int endImage)
  484.     throws MalformedURLException {
  485.     Vector result = new Vector(Math.abs(endImage - startImage) + 1);
  486.     if (startImage > endImage) {
  487.         for (int i = startImage; i >= endImage; i--) {
  488.         result.addElement(new URL(imageSource, "T"+i+".gif"));
  489.         }
  490.     } else {
  491.         for (int i = startImage; i <= endImage; i++) {
  492.         result.addElement(new URL(imageSource, "T"+i+".gif"));
  493.         }
  494.     }
  495.     return result;
  496.     }
  497.  
  498.     
  499.     /**
  500.      * Initialize the applet.  Get parameters.
  501.      */
  502.     public void init() {
  503.  
  504.     try {
  505.         String param = getParameter("IMAGESOURCE");    
  506.         imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
  507.         dbg("IMAGESOURCE = "+param);
  508.     
  509.         param = getParameter("PAUSE");
  510.         globalPause =
  511.         (param != null) ? Integer.parseInt(param) : defaultPause;
  512.         dbg("PAUSE = "+param);
  513.  
  514.         param = getParameter("REPEAT");
  515.         repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
  516.                            param.equalsIgnoreCase("true"));
  517.  
  518.         int startImage = 1;
  519.         int endImage = 1;
  520.         param = getParameter("ENDIMAGE");
  521.         dbg("ENDIMAGE = "+param);
  522.         if (param != null) {
  523.         endImage = Integer.parseInt(param);
  524.         param = getParameter("STARTIMAGE");
  525.         dbg("STARTIMAGE = "+param);
  526.         if (param != null) {
  527.             startImage = Integer.parseInt(param);
  528.         }
  529.         images = prepareImageRange(startImage, endImage);
  530.         } else {
  531.         param = getParameter("STARTIMAGE");
  532.         dbg("STARTIMAGE = "+param);
  533.         if (param != null) {
  534.             startImage = Integer.parseInt(param);
  535.             images = prepareImageRange(startImage, endImage);
  536.         } else {
  537.             param = getParameter("IMAGES");
  538.             if (param == null) {
  539.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  540.                    "specified.");
  541.             return;
  542.             } else {
  543.             images = parseImages(param);
  544.             }
  545.         }
  546.         }
  547.  
  548.         param = getParameter("BACKGROUND");
  549.         dbg("BACKGROUND = "+param);
  550.         if (param != null) {
  551.         backgroundImageURL = new URL(imageSource, param);
  552.         }
  553.  
  554.         param = getParameter("STARTUP");
  555.         dbg("STARTUP = "+param);
  556.         if (param != null) {
  557.         startUpImageURL = new URL(imageSource, param);
  558.         }
  559.  
  560.         param = getParameter("SOUNDSOURCE");
  561.         soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
  562.         dbg("SOUNDSOURCE = "+param);
  563.     
  564.         param = getParameter("SOUNDS");
  565.         dbg("SOUNDS = "+param);
  566.         if (param != null) {
  567.         sounds = parseSounds(param, images);
  568.         }
  569.  
  570.         param = getParameter("PAUSES");
  571.         dbg("PAUSES = "+param);
  572.         if (param != null) {
  573.         durations = parseDurations(param, images);
  574.         }
  575.  
  576.         param = getParameter("POSITIONS");
  577.         dbg("POSITIONS = "+param);
  578.         if (param != null) {
  579.         positions = parsePositions(param, images);
  580.         }
  581.  
  582.         param = getParameter("SOUNDTRACK");
  583.         dbg("SOUNDTRACK = "+param);
  584.         if (param != null) {
  585.         soundtrackURL = new URL(soundSource, param);
  586.         }
  587.     } catch (MalformedURLException e) {
  588.         showParseError(e);
  589.     } catch (ParseException e) {
  590.         showParseError(e);
  591.     }
  592.     
  593.  
  594.  
  595.     setFrameNum(0);
  596.     }
  597.  
  598.     void tellLoadingMsg(String file, String fileType) {
  599.     showStatus("Animator: loading "+fileType+" "+abridge(file, 20));
  600.     }
  601.  
  602.     void tellLoadingMsg(URL url, String fileType) {
  603.     tellLoadingMsg(url.toExternalForm(), fileType);
  604.     }
  605.  
  606.     void clearLoadingMessage() {
  607.     showStatus("");
  608.     }
  609.     
  610.     /**
  611.      * Cut the string down to length=len, while still keeping it readable.
  612.      */
  613.     static String abridge(String s, int len) {
  614.     String ellipsis = "...";
  615.  
  616.     if (len >= s.length()) {
  617.         return s;
  618.     }
  619.  
  620.     int trim = len - ellipsis.length();
  621.     return s.substring(0, trim / 2)+ellipsis+
  622.         s.substring(s.length() - trim / 2);
  623.     }
  624.     
  625.     void loadError(URL badURL, String fileType) {
  626.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  627.         badURL.toExternalForm();
  628.     showStatus(errorMsg);
  629.     System.err.println(errorMsg);
  630.     error = true;
  631.     repaint();
  632.     }
  633.  
  634.     void showParseError(Exception e) {
  635.     String errorMsg = "Animator: Parse error: "+e;
  636.     showStatus(errorMsg);
  637.     System.err.println(errorMsg);
  638.     error = true;
  639.     repaint();
  640.     }
  641.  
  642.     void startPlaying() {
  643.     if (soundtrack != null) {
  644.         soundtrack.loop();
  645.     }
  646.     }
  647.  
  648.     void stopPlaying() {
  649.     if (soundtrack != null) {
  650.         soundtrack.stop();
  651.     }
  652.     }
  653.  
  654.     /**
  655.      * Run the animation. This method is called by class Thread.
  656.      * @see java.lang.Thread
  657.      */
  658.     public void run() {
  659.     Thread me = Thread.currentThread();
  660.  
  661.     me.setPriority(Thread.MIN_PRIORITY);
  662.  
  663.     if (! loaded) {
  664.         try {
  665.         // ... to do a bunch of loading.
  666.         if (startUpImageURL != null) {
  667.             tellLoadingMsg(startUpImageURL, imageLabel);
  668.             startUpImage = getImage(startUpImageURL);
  669.             try {
  670.             updateMaxDims(getImageDimensions(startUpImage));
  671.             } catch (Exception e) {
  672.             loadError(startUpImageURL, "start-up image");
  673.             }
  674.             resize(maxWidth, maxHeight);
  675.             repaint();
  676.         }
  677.  
  678.         if (backgroundImageURL != null) {
  679.             tellLoadingMsg(backgroundImageURL, imageLabel);
  680.             backgroundImage = getImage(backgroundImageURL);
  681.             repaint();
  682.             try {
  683.             updateMaxDims(
  684.                getImageDimensions(backgroundImage));
  685.             } catch (Exception e) {
  686.             loadError(backgroundImageURL, "background image");
  687.             }
  688.         }
  689.  
  690.         URL badURL = fetchImages(images);
  691.         if (badURL != null) {
  692.             loadError(badURL, imageLabel);
  693.             return;
  694.         }
  695.  
  696.         if (soundtrackURL != null && soundtrack == null) {
  697.             tellLoadingMsg(soundtrackURL, imageLabel);
  698.             soundtrack = getAudioClip(soundtrackURL);
  699.             if (soundtrack == null) {
  700.             loadError(soundtrackURL, "soundtrack");
  701.             return;
  702.             }
  703.         }
  704.  
  705.         if (sounds != null) {
  706.             badURL = fetchSounds(sounds);
  707.             if (badURL != null) {
  708.             loadError(badURL, soundLabel);
  709.             return;
  710.             }
  711.         }
  712.  
  713.         clearLoadingMessage();
  714.  
  715.         offScrImage = createImage(maxWidth, maxHeight);
  716.         offScrGC = offScrImage.getGraphics();
  717.         offScrGC.setColor(Color.lightGray);
  718.  
  719.         resize(maxWidth, maxHeight);
  720.         loaded = true;
  721.         error = false;
  722.         } catch (Exception e) {
  723.         error = true;
  724.         e.printStackTrace();
  725.         }
  726.     }
  727.  
  728.     if (userPause) {
  729.         return;
  730.     }
  731.  
  732.     if (repeat || frameNum < images.size()) {
  733.         startPlaying();
  734.     }
  735.  
  736.     try {
  737.         if (images.size() > 1) {
  738.         while (maxWidth > 0 && maxHeight > 0 && engine == me) {
  739.             if (frameNum >= images.size()) {
  740.             if (!repeat) {
  741.                 return;
  742.             }
  743.             setFrameNum(0);
  744.             }
  745.             repaint();
  746.  
  747.             if (sounds != null) {
  748.             AudioClip clip =
  749.                 (AudioClip)sounds.get(frameNumKey);
  750.             if (clip != null) {
  751.                 clip.play();
  752.             }
  753.             }
  754.  
  755.             try {
  756.             Integer pause = null;
  757.             if (durations != null) {
  758.                 pause = (Integer)durations.get(frameNumKey);
  759.             }
  760.             if (pause == null) {
  761.                 Thread.sleep(globalPause);
  762.             } else {
  763.                 Thread.sleep(pause.intValue());
  764.             }
  765.             } catch (InterruptedException e) {
  766.             // Should we do anything?
  767.             }
  768.             setFrameNum(frameNum+1);
  769.         }
  770.         }
  771.     } finally {
  772.         stopPlaying();
  773.     }
  774.     }
  775.  
  776.     /**
  777.      * Paint the current frame.
  778.      */
  779.     public void paint(Graphics g) {
  780.     if (error || !loaded) {
  781.         if (startUpImage != null) {
  782.         g.drawImage(startUpImage, 0, 0, this);
  783.         } else {
  784.         if (backgroundImage != null) {
  785.             g.drawImage(backgroundImage, 0, 0, this);
  786.         } else {
  787.             g.clearRect(0, 0, maxWidth, maxHeight);
  788.         }
  789.         }
  790.     } else {
  791.         if ((images != null) && (images.size() > 0)) {
  792.         if (frameNum < images.size()) {
  793.             if (backgroundImage == null) {
  794.             offScrGC.fillRect(0, 0, maxWidth, maxHeight);
  795.             } else {
  796.             offScrGC.drawImage(backgroundImage, 0, 0, this);
  797.             }
  798.  
  799.             Image image = (Image)images.elementAt(frameNum);
  800.             Point pos = null;
  801.             if (positions != null) {
  802.             pos = (Point)positions.get(frameNumKey);
  803.             }
  804.             if (pos != null) {
  805.             xPos = pos.x;
  806.             yPos = pos.y;
  807.             }
  808.             offScrGC.drawImage(image, xPos, yPos, this);
  809.             g.drawImage(offScrImage, 0, 0, this);
  810.         } else {
  811.             // no more animation, but need to draw something
  812.             dbg("No more animation; drawing last image.");
  813.             g.drawImage((Image)images.lastElement(), 0, 0, this);
  814.         }
  815.         }
  816.     }
  817.     }
  818.  
  819.     /**
  820.      * Start the applet by forking an animation thread.
  821.      */
  822.     public void start() {
  823.     if (engine == null) {
  824.         engine = new Thread(this);
  825.         engine.start();
  826.     }
  827.     }
  828.  
  829.     /**
  830.      * Stop the insanity, um, applet.
  831.      */
  832.     public void stop() {
  833.     if (engine != null && engine.isAlive()) {
  834.         engine.stop();
  835.     }
  836.     engine = null;
  837.     }
  838.  
  839.     /**
  840.      * Pause the thread when the user clicks the mouse in the applet.
  841.      * If the thread has stopped (as in a non-repeat performance),
  842.      * restart it.
  843.      */
  844.     public boolean handleEvent(Event evt) {
  845.     if (evt.id == Event.MOUSE_DOWN) {
  846.         if (loaded) {
  847.         if (engine != null && engine.isAlive()) {
  848.             if (userPause) {
  849.             engine.resume();
  850.             startPlaying();
  851.             } else {
  852.             engine.suspend();
  853.             stopPlaying();
  854.             }
  855.             userPause = !userPause;
  856.         } else {
  857.             userPause = false;
  858.             setFrameNum(0);
  859.             engine = new Thread(this);
  860.             engine.start();
  861.         }
  862.         }
  863.         return true;
  864.     } else {        
  865.         return super.handleEvent(evt);
  866.     }
  867.     }
  868.     
  869. }
  870.  
  871.  
  872. class ParseException extends Exception {
  873.     ParseException(String s) {
  874.     super(s);
  875.     }
  876. }
  877.  
  878. class ImageNotFoundException extends Exception {
  879.     ImageNotFoundException(ImageProducer source) {
  880.     super(source+"");
  881.     }
  882. }
  883.  
  884.