home *** CD-ROM | disk | FTP | other *** search
/ Java 1996 August / Java - Summer 1996.iso / rockridge / betaclasses / Animator.java < prev    next >
Encoding:
Java Source  |  1995-11-13  |  20.4 KB  |  875 lines

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