home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / JBuilder8.iso / Solaris / resource / jre / demo / plugin / applets / Animator / Animator.java < prev    next >
Encoding:
Java Source  |  2002-09-06  |  28.2 KB  |  877 lines

  1. /*
  2.  * Copyright (c) 2002 Sun Microsystems, Inc. All  Rights Reserved.
  3.  * 
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  * 
  8.  * -Redistributions of source code must retain the above copyright
  9.  *  notice, this list of conditions and the following disclaimer.
  10.  * 
  11.  * -Redistribution in binary form must reproduct the above copyright
  12.  *  notice, this list of conditions and the following disclaimer in
  13.  *  the documentation and/or other materials provided with the distribution.
  14.  * 
  15.  * Neither the name of Sun Microsystems, Inc. or the names of contributors
  16.  * may be used to endorse or promote products derived from this software
  17.  * without specific prior written permission.
  18.  * 
  19.  * This software is provided "AS IS," without a warranty of any kind. ALL
  20.  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
  21.  * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
  22.  * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT
  23.  * BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT
  24.  * OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR ITS
  25.  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
  26.  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
  27.  * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
  28.  * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN
  29.  * IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
  30.  * 
  31.  * You acknowledge that Software is not designed, licensed or intended for
  32.  * use in the design, construction, operation or maintenance of any nuclear
  33.  * facility.
  34.  */
  35.  
  36. /*
  37.  * @(#)Animator.java    1.8 02/06/13
  38.  */
  39.  
  40. import java.awt.*;
  41. import java.awt.event.*;
  42. import java.applet.Applet;
  43. import java.applet.AudioClip;
  44. import java.util.Vector;
  45. import java.util.Hashtable;
  46. import java.util.Enumeration;
  47. import java.net.URL;
  48. import java.net.MalformedURLException;
  49. import java.util.List;
  50. import java.util.ArrayList;
  51. import java.util.Iterator;
  52.  
  53. /**
  54.  * An applet that plays a sequence of images, as a loop or a one-shot.
  55.  * Can have a soundtrack and/or sound effects tied to individual frames.
  56.  * See the <a href="http://java.sun.com/applets/applets/Animator/">Animator
  57.  * home page</a> for details and updates.
  58.  *
  59.  * @author Herb Jellinek
  60.  * @version 1.8, 06/13/02
  61.  */
  62. public class Animator extends Applet implements Runnable, MouseListener {
  63.     int appWidth = 0;                // Animator width
  64.     int appHeight = 0;               // Animator height
  65.     Thread engine = null;            // Thread animating the images
  66.     boolean userPause = false;       // True if thread currently paused by user
  67.     boolean loaded = false;          // Can we paint yet?
  68.     boolean error = false;           // Was there an initialization error?
  69.     Animation animation = null;      // Animation this animator contains
  70.     String hrefTarget = null;        // Frame target of reference URL if any
  71.     URL hrefURL = null;              // URL link for information if any
  72.  
  73.     static final String sourceLocation = 
  74.                           "http://java.sun.com/applets/applets/Animator/";
  75.     static final String userInstructions = "shift-click for errors, info";
  76.     static final int STARTUP_ID    = 0;
  77.     static final int BACKGROUND_ID = 1;
  78.     static final int ANIMATION_ID  = 2;
  79.  
  80.     /**
  81.      * Applet info.
  82.      */
  83.     public String getAppletInfo() {
  84.     return "Animator v1.10 (02/05/97), by Herb Jellinek";
  85.     }
  86.  
  87.     /**
  88.      * Parameter info.
  89.      */
  90.     public String[][] getParameterInfo() {
  91.     String[][] info = {
  92.         {"imagesource",     "URL",            "a directory"},
  93.         {"startup",     "URL",            "image displayed at start-up"},
  94.         {"backgroundcolor", "int", "background color (24-bit RGB number)"},
  95.         {"background",     "URL",           "image displayed as background"},
  96.         {"startimage",     "int",            "index of first image"},
  97.         {"endimage",     "int",            "index of last image"},
  98.         {"namepattern",     "URL",         "generates indexed names"},
  99.         {"images",          "URLs",        "list of image indices"},
  100.         {"href",        "URL",           "page to visit on mouse-click"},
  101.         {"target",        "name",           "frame to put that page in"},
  102.         {"pause",             "int",            "global pause, milliseconds"},
  103.         {"pauses",             "ints",     "individual pauses, milliseconds"},
  104.         {"repeat",             "boolean",     "repeat? true or false"},
  105.         {"positions",       "coordinates", "path images will follow"},
  106.         {"soundsource",    "URL",            "audio directory"},
  107.         {"soundtrack",    "URL",            "background music"},
  108.         {"sounds",        "URLs",           "list of audio samples"},
  109.     };
  110.     return info;
  111.     }
  112.  
  113.     /**
  114.      * Show a crude "About" box.  Displays credits, errors (if any), and
  115.      * parameter values and documentation.
  116.      */
  117.     void showDescription() {
  118.     DescriptionFrame description = new DescriptionFrame();        
  119.     description.tell("\t\t"+getAppletInfo()+"\n");
  120.     description.tell("Updates, documentation at "+sourceLocation+"\n\n");
  121.     description.tell("Document base: "+getDocumentBase()+"\n");
  122.     description.tell("Code base: "+getCodeBase()+"\n\n");
  123.     
  124.     Object errors[] = animation.tracker.getErrorsAny();
  125.     if (errors != null) {
  126.         description.tell("Applet image errors:\n");
  127.         for (int i = 0; i < errors.length; i++) {
  128.         if (errors[i] instanceof Image) {
  129.             AnimationFrame frame = (AnimationFrame) 
  130.                                               animation.frames.get(i);
  131.                     URL url = frame.imageLocation;
  132.             if (url != null) {
  133.             description.tell(" "+url+" not loaded\n");
  134.             }
  135.         }
  136.         }
  137.         description.tell("\n");
  138.     }
  139.     if (animation.frames == null || animation.frames.size() == 0)
  140.         description.tell("\n** No images loaded **\n\n");
  141.     description.tell("Applet parameters:\n");
  142.     description.tell(" width = "+getParameter("WIDTH")+"\n");
  143.     description.tell(" height = "+getParameter("HEIGHT")+"\n");
  144.     String params[][] = getParameterInfo();
  145.     for (int i = 0; i < params.length; i++) {
  146.         String name = params[i][0];
  147.         description.tell(" "+name+" = "+getParameter(name)+
  148.                  "\t ["+params[i][2]+"]\n");
  149.     }
  150.     description.show();
  151.     }
  152.  
  153.     /**
  154.      * Local version of getParameter for debugging purposes.
  155.      */
  156.     public String getParam(String key) {
  157.     String result = getParameter(key);
  158.     return result;
  159.     }
  160.  
  161.     /**
  162.      * Get parameters and parse them
  163.      */
  164.     public void handleParams() {
  165.         try {
  166.         String param = getParam("IMAGESOURCE");
  167.         animation.imageSource = (param == null) ? getDocumentBase() :
  168.                 new URL(getDocumentBase(), param + "/");
  169.     
  170.         String href = getParam("HREF");
  171.         if (href != null) {
  172.         try {
  173.             hrefURL = new URL(getDocumentBase(), href);
  174.         } catch (MalformedURLException e) {
  175.             showParseError(e);
  176.         }
  177.         }
  178.  
  179.         hrefTarget = getParam("TARGET");
  180.         if (hrefTarget == null)
  181.         hrefTarget = "_top";
  182.         param = getParam("PAUSE");
  183.             if (param != null)
  184.                 animation.setGlobalPause(Integer.parseInt(param));
  185.         param = getParam("REPEAT");
  186.         animation.repeat = (param == null) ? true :
  187.                 (param.equalsIgnoreCase("yes") ||
  188.                  param.equalsIgnoreCase("true"));
  189.         int startImage = 1;
  190.         int endImage = 1;
  191.         param = getParam("ENDIMAGE");
  192.         if (param != null) {
  193.         endImage = Integer.parseInt(param);
  194.         param = getParam("STARTIMAGE");
  195.         if (param != null) {
  196.             startImage = Integer.parseInt(param);
  197.         }
  198.                 param = getParam("NAMEPATTERN");
  199.                 animation.prepareImageRange(startImage, endImage, param);
  200.         } else {
  201.         param = getParam("STARTIMAGE");
  202.         if (param != null) {
  203.             startImage = Integer.parseInt(param);
  204.             param = getParam("NAMEPATTERN");
  205.             animation.prepareImageRange(startImage, endImage, param);
  206.         } else {
  207.             param = getParam("IMAGES");
  208.             if (param == null) {
  209.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  210.                    "specified.");
  211.             error = true;
  212.             return;
  213.             } else {
  214.             animation.parseImages(param, getParam("NAMEPATTERN"));
  215.             }
  216.         }
  217.         }
  218.  
  219.         param = getParam("BACKGROUND");
  220.         if (param != null)
  221.         animation.backgroundImageURL = new URL(animation.imageSource,
  222.                                                        param);
  223.         param = getParam("BACKGROUNDCOLOR");
  224.         if (param != null)
  225.         animation.backgroundColor = decodeColor(param);
  226.         param = getParam("STARTUP");
  227.         if (param != null)
  228.         animation.startUpImageURL = new URL(animation.imageSource, 
  229.                                                     param);
  230.         param = getParam("SOUNDSOURCE");
  231.         animation.soundSource = (param == null) ? animation.imageSource :
  232.                 new URL(getDocumentBase(), param + "/");    
  233.         param = getParam("SOUNDS");
  234.         if (param != null)
  235.         animation.parseSounds(param);
  236.         param = getParam("PAUSES");
  237.         if (param != null)
  238.         animation.parseDurations(param);
  239.         param = getParam("POSITIONS");
  240.         if (param != null) 
  241.         animation.parsePositions(param);
  242.         param = getParam("SOUNDTRACK");
  243.         if (param != null)
  244.         animation.soundTrackURL = new URL(
  245.                                           animation.soundSource, param);
  246.     } catch (MalformedURLException e) {
  247.         showParseError(e);
  248.     } catch (ParseException e) {
  249.         showParseError(e);
  250.     }
  251.     }
  252.  
  253.     private Color decodeColor(String s) {
  254.     int val = 0;
  255.     try {
  256.         if (s.startsWith("0x")) {
  257.         val = Integer.parseInt(s.substring(2), 16);
  258.         } else if (s.startsWith("#")) {
  259.         val = Integer.parseInt(s.substring(1), 16);
  260.         } else if (s.startsWith("0") && s.length() > 1) {
  261.         val = Integer.parseInt(s.substring(1), 8);
  262.         } else {
  263.         val = Integer.parseInt(s, 10);
  264.         }
  265.         return new Color(val);
  266.     } catch (NumberFormatException e) {
  267.         return null;
  268.     }
  269.     }
  270.     
  271.     /**
  272.      * Initialize the applet.  Get parameters.
  273.      */
  274.     public void init() {
  275.         
  276.         //animation.tracker = new MediaTracker(this);
  277.     appWidth = getSize().width;
  278.     appHeight = getSize().height;
  279.         animation = new Animation(this);
  280.         handleParams();
  281.         animation.init();
  282.     addMouseListener(this);
  283.         Thread me = Thread.currentThread();
  284.         me.setPriority(Thread.MIN_PRIORITY);
  285.         userPause = false;
  286.     }
  287.  
  288.     public void destroy() {
  289.         removeMouseListener(this);
  290.     }
  291.  
  292.     void tellLoadingMsg(String file, String fileType) {
  293.     showStatus("Animator: loading "+fileType+" "+file);
  294.     }
  295.  
  296.     void tellLoadingMsg(URL url, String fileType) {
  297.     tellLoadingMsg(url.toExternalForm(), fileType);
  298.     }
  299.  
  300.     void clearLoadingMessage() {
  301.     showStatus("");
  302.     }
  303.     
  304.     void loadError(String fileName, String fileType) {
  305.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  306.         fileName;
  307.     showStatus(errorMsg);
  308.     System.err.println(errorMsg);
  309.     error = true;
  310.     repaint();
  311.     }
  312.  
  313.     void loadError(URL badURL, String fileType) {
  314.     loadError(badURL.toExternalForm(), fileType);
  315.     }
  316.  
  317.     void showParseError(Exception e) {
  318.     String errorMsg = "Animator: Parse error: "+e;
  319.     showStatus(errorMsg);
  320.     System.err.println(errorMsg);
  321.     error = true;
  322.     repaint();
  323.     }
  324.  
  325.     /**
  326.      * Run the animation. This method is called by class Thread.
  327.      * @see java.lang.Thread
  328.      */
  329.     public void run() {
  330.         Thread me = Thread.currentThread();
  331.         if (animation.frames == null)
  332.             return;
  333.         if ((appWidth <= 0) || (appHeight <= 0))
  334.             return;
  335.     try {
  336.             while (engine == me) {
  337.                 // Get current frame and paint it, play its sound
  338.                 AnimationFrame thisFrame = (AnimationFrame) 
  339.                                animation.frames.get(animation.currentFrame);
  340.                 repaint();
  341.                 if (thisFrame.sound != null)
  342.                     thisFrame.sound.play();
  343.  
  344.                 animation.currentFrame++;
  345.                  // Check if we are done
  346.                 if (animation.currentFrame >= animation.frames.size()) {
  347.                     if (animation.repeat)
  348.                         animation.currentFrame = 0;
  349.                     else return;
  350.                 }
  351.  
  352.                 // Pause for duration or longer if user paused
  353.                 try {        
  354.                     Thread.sleep(thisFrame.duration);
  355.                     synchronized(this) {
  356.                         while (userPause) {
  357.                             animation.stopPlaying();
  358.                             wait();
  359.                         }
  360.                     }
  361.                 }
  362.                 catch (InterruptedException e) {
  363.                 }
  364.             }
  365.         } finally {
  366.             synchronized(this) {
  367.             if (engine == me)
  368.                 animation.stopPlaying();
  369.             }
  370.         }
  371.     }
  372.  
  373.     /**
  374.      * No need to clear anything; just paint.
  375.      */
  376.     public void update(Graphics g) {
  377.     paint(g);
  378.     }
  379.  
  380.     /**
  381.      * Paint the current frame
  382.      */
  383.     public void paint(Graphics g) {
  384.         if (error || ! loaded) {
  385.             if (animation.startUpImage != null) {
  386.         if (animation.tracker.checkID(STARTUP_ID)) {
  387.             if (animation.backgroundColor != null) {
  388.             g.setColor(animation.backgroundColor);
  389.             g.fillRect(0, 0, appWidth, appHeight);
  390.             }
  391.             g.drawImage(animation.startUpImage, 0, 0, this);
  392.         }
  393.         } else {
  394.         if ((animation.backgroundImage != null) &&
  395.              (animation.tracker.checkID(BACKGROUND_ID)))
  396.             g.drawImage(animation.backgroundImage, 0, 0, this);
  397.         else 
  398.             g.clearRect(0, 0, appWidth, appHeight);
  399.         }
  400.         } else {
  401.             animation.paint(g);
  402.         }
  403.     }
  404.  
  405.     /**
  406.      * Start the applet by forking an animation thread.
  407.      */
  408.     public void start() {
  409.     engine = new Thread(this);
  410.     engine.start();
  411.     showStatus(getAppletInfo());
  412.     }
  413.  
  414.     /**
  415.      * Stop the insanity, um, applet.
  416.      */
  417.     public synchronized void stop() {
  418.     engine = null;
  419.         animation.stopPlaying();
  420.          if (userPause) {
  421.             userPause = false;
  422.             notify();
  423.         }
  424.     }
  425.  
  426.     /**
  427.      * Pause the thread when the user clicks the mouse in the applet.
  428.      * If the thread has stopped (as in a non-repeat performance),
  429.      * restart it.
  430.      */
  431.     public synchronized void mousePressed(MouseEvent event) {
  432.         event.consume();
  433.     if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
  434.         showDescription();
  435.         return;
  436.     } else if (hrefURL != null) {
  437.             //Let mouseClicked handle this.
  438.             return;
  439.         } else if (loaded) {
  440.         userPause = !userPause;
  441.             if (!userPause) {
  442.                 animation.startPlaying();
  443.                 notifyAll();
  444.             }
  445.     }
  446.     }
  447.  
  448.     public void mouseClicked(MouseEvent event) {
  449.     if ((hrefURL != null) &&
  450.             ((event.getModifiers() & InputEvent.SHIFT_MASK) == 0)) {
  451.         getAppletContext().showDocument(hrefURL, hrefTarget);
  452.     }
  453.     }
  454.  
  455.     public void mouseReleased(MouseEvent event) {
  456.     }
  457.  
  458.     public void mouseEntered(MouseEvent event) {
  459.     showStatus(getAppletInfo()+" -- "+userInstructions);
  460.     }
  461.  
  462.     public void mouseExited(MouseEvent event) {
  463.         showStatus("");
  464.     }    
  465. }
  466.  
  467. /**
  468.  * A class that represents an animation to be displayed by the applet
  469.  */
  470. class Animation extends Object {
  471.     static final int STARTUP_ID    = 0;
  472.     static final int BACKGROUND_ID = 1;
  473.     static final int ANIMATION_ID  = 2;
  474.     static final String imageLabel = "image";
  475.     static final String soundLabel = "sound";
  476.  
  477.     int globalPause = 1300;        // global pause in milleseconds
  478.     List frames = null;            // List holding frames of animation
  479.     int currentFrame;              // Index into images for current position
  480.     Image startUpImage = null;     // The startup image if any
  481.     Image backgroundImage = null;  // The background image if any
  482.     AudioClip soundTrack = null;   // The soundtrack for this animation
  483.     Color backgroundColor = null;  // Background color if any
  484.     URL backgroundImageURL = null; // URL of background image if any
  485.     URL startUpImageURL = null;    // URL of startup image if any 
  486.     URL soundTrackURL = null;      // URL of soundtrack
  487.     URL imageSource = null;        // Directory or URL for images
  488.     URL soundSource = null;        // Directory or URL for sounds
  489.     boolean repeat;                // Repeat the animation if true
  490.     Image offScrImage;             // Offscreen image
  491.     Graphics offScrGC;             // Offscreen graphics context
  492.     MediaTracker tracker;          // MediaTracker used to load images
  493.     Animator owner;                // Applet that contains this animation
  494.  
  495.     Animation(Animator container) {
  496.         super();
  497.         owner = container;
  498.     }
  499.  
  500.     void init() {
  501.         tracker = new MediaTracker(owner);
  502.     currentFrame = 0;
  503.         loadAnimationMedia();
  504.     startPlaying();
  505.     }
  506.  
  507.     void setGlobalPause(int pause) {
  508.         globalPause = pause;
  509.     }
  510.  
  511.     /**
  512.      * Loads the images and sounds involved with this animation
  513.      */
  514.     void loadAnimationMedia() {
  515.         URL badURL;
  516.         boolean error;
  517.         try {
  518.             if (startUpImageURL != null) {
  519.                 owner.tellLoadingMsg(startUpImageURL, imageLabel);
  520.                 startUpImage = fetchImageAndWait(startUpImageURL, STARTUP_ID);
  521.                 if (tracker.isErrorID(STARTUP_ID)) {
  522.                     owner.loadError(startUpImageURL, "start-up image");
  523.                 }
  524.                 owner.repaint();
  525.             }
  526.         
  527.         if (backgroundImageURL != null) {
  528.              owner.tellLoadingMsg(backgroundImageURL, imageLabel);
  529.             backgroundImage = fetchImageAndWait(backgroundImageURL, 
  530.                                                     BACKGROUND_ID);
  531.             if (tracker.isErrorID(BACKGROUND_ID))
  532.                     owner.loadError(backgroundImageURL, 
  533.                                         "background image");
  534.         owner.repaint();
  535.             }
  536.  
  537.         // Fetch the animation frame images
  538.             Iterator iterator = frames.iterator();
  539.             while(iterator.hasNext()) {
  540.                 AnimationFrame frame = (AnimationFrame) iterator.next();
  541.                 owner.tellLoadingMsg(frame.imageLocation, imageLabel);
  542.                 frame.image = owner.getImage(frame.imageLocation);
  543.                 tracker.addImage(frame.image, ANIMATION_ID);
  544.                 try {
  545.                     tracker.waitForID(ANIMATION_ID);
  546.                 } catch (InterruptedException e) {}
  547.         }
  548.  
  549.         if (soundTrackURL != null && soundTrack == null) {
  550.         owner.tellLoadingMsg(soundTrackURL, imageLabel);
  551.         soundTrack = owner.getAudioClip(soundTrackURL);
  552.         if (soundTrack == null) {
  553.             owner.loadError(soundTrackURL, "soundtrack");
  554.             return;
  555.         }
  556.         }
  557.  
  558.             // Load the sounds into their frames
  559.             iterator = frames.iterator();
  560.             while(iterator.hasNext()) {
  561.                 AnimationFrame frame = (AnimationFrame) iterator.next();
  562.                 if (frame.soundLocation != null) {
  563.                     owner.tellLoadingMsg(frame.soundLocation, soundLabel);
  564.                     try {
  565.                         frame.sound = owner.getAudioClip(frame.soundLocation);
  566.                     } catch (Exception ex) {
  567.                         owner.loadError(frame.soundLocation, soundLabel);
  568.                     }
  569.                 }
  570.         }
  571.  
  572.         owner.clearLoadingMessage();
  573.         offScrImage = owner.createImage(owner.appWidth, owner.appHeight);
  574.         offScrGC = offScrImage.getGraphics();
  575.         offScrGC.setColor(Color.lightGray);
  576.             owner.loaded = true;
  577.         error = false;
  578.     } catch (Exception e) {
  579.         error = true;
  580.         e.printStackTrace();
  581.     }
  582.     }
  583.  
  584.     /**
  585.      * Fetch an image and wait for it to come in.  Used to enforce a load
  586.      * order for background and startup images.
  587.      */
  588.     Image fetchImageAndWait(URL imageURL, int trackerClass) 
  589.                               throws InterruptedException {
  590.     Image image = owner.getImage(imageURL);
  591.     tracker.addImage(image, trackerClass);
  592.     tracker.waitForID(trackerClass);
  593.     return image;
  594.     }
  595.  
  596.     /**
  597.      * Stuff a range of image names into a List
  598.      * @return a List of image URLs.
  599.      */
  600.     void prepareImageRange(int startImage, int endImage, String pattern)
  601.     throws MalformedURLException {
  602.     frames = new ArrayList(Math.abs(endImage - startImage) + 1);
  603.     if (pattern == null)
  604.         pattern = "T%N.gif";
  605.     if (startImage > endImage) {
  606.         for (int i = startImage; i >= endImage; i--) {
  607.                 AnimationFrame frame = new AnimationFrame();
  608.         frames.add(frame);
  609.                 frame.duration = globalPause;
  610.                 frame.imageLocation = new URL(imageSource, 
  611.                                               doSubst(pattern, i+""));
  612.         }
  613.     } else {
  614.         for (int i = startImage; i <= endImage; i++) {
  615.                 AnimationFrame frame = new AnimationFrame();
  616.         frames.add(frame);
  617.                 frame.duration = globalPause;
  618.                 frame.imageLocation = new URL(imageSource,
  619.                                               doSubst(pattern, i+""));
  620.         }
  621.     }
  622.     }
  623.  
  624.     /**
  625.      * Parse the SOUNDS parameter.  It looks like
  626.      * train.au||hello.au||stop.au, etc., where each item refers to a
  627.      * source image.  Empty items mean that the corresponding image
  628.      * has no associated sound.
  629.      */
  630.     void parseSounds(String attr) throws MalformedURLException {
  631.     int frameIndex = 0;
  632.     int numFrames = frames.size();
  633.     for (int i = 0; (i < attr.length()) && (frameIndex < numFrames); ) {
  634.         int next = attr.indexOf('|', i);
  635.         if (next == -1) next = attr.length();
  636.         String sound = attr.substring(i, next);
  637.         if (sound.length() != 0) {
  638.                 AnimationFrame frame = (AnimationFrame) frames.get(frameIndex);
  639.         frame.soundLocation = new URL(soundSource, sound);
  640.         }
  641.         i = next + 1;
  642.         frameIndex++;
  643.         }
  644.     }
  645.  
  646.     /**
  647.      * Parse the IMAGES parameter.  It looks like
  648.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  649.      */
  650.     void parseImages(String attr, String pattern) 
  651.                            throws MalformedURLException {
  652.         frames = new ArrayList();
  653.     if (pattern == null)
  654.         pattern = "T%N.gif";
  655.     for (int i = 0; i < attr.length(); ) {
  656.         int next = attr.indexOf('|', i);
  657.         if (next == -1) next = attr.length();
  658.         String file = attr.substring(i, next);
  659.             AnimationFrame frame = new AnimationFrame();
  660.             frames.add(frame);
  661.             frame.imageLocation = new URL(imageSource, doSubst(pattern, file));
  662.             frame.duration = globalPause;
  663.         i = next + 1;
  664.     }
  665.     }
  666.  
  667.     /**
  668.      * Parse the PAUSES parameter.  It looks like
  669.      * 1000|500|||750, etc., where each item corresponds to a
  670.      * source image.  Empty items mean that the corresponding image
  671.      * has no special duration, and should use the global one.
  672.      *
  673.      * @return a Hashtable of Integer pauses keyed to Integer
  674.      * frame numbers.
  675.      */
  676.     void parseDurations(String attr) {
  677.     int imageNum = 0;
  678.     int numImages = frames.size();
  679.     for (int i = 0; (i < attr.length()) && (imageNum < numImages); ) {
  680.         int next = attr.indexOf('|', i);
  681.         if (next == -1) next = attr.length();
  682.             AnimationFrame aFrame = (AnimationFrame) frames.get(imageNum);
  683.         if (i != next) {
  684.         int duration = Integer.parseInt(attr.substring(i, next));
  685.                 aFrame.duration = duration;
  686.         }
  687.         i = next + 1;
  688.         imageNum++;
  689.     }
  690.     }
  691.  
  692.     /**
  693.      * Parse a String of form xxx@yyy and return a Point.
  694.      */
  695.     Point parsePoint(String s) throws ParseException {
  696.     int atPos = s.indexOf('@');
  697.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  698.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  699.              Integer.parseInt(s.substring(atPos + 1)));
  700.     }
  701.  
  702.     /**
  703.      * Parse the POSITIONS parameter.  It looks like
  704.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  705.      * corresponding to a source image.  Empty items mean that the
  706.      * corresponding image has the same position as the preceding one.
  707.      *
  708.      * @return a Hashtable of Points keyed to Integer frame numbers.
  709.      */
  710.     void parsePositions(String param)
  711.     throws ParseException {
  712.     int imageNum = 0;
  713.     int numImages = frames.size();
  714.     for (int i = 0; (i < param.length()) && (imageNum < numImages); ) {
  715.         int next = param.indexOf('|', i);
  716.         if (next == -1) 
  717.                 next = param.length();
  718.         if (i != next) {
  719.                 AnimationFrame frame = (AnimationFrame) frames.get(imageNum);
  720.                 frame.position = parsePoint(param.substring(i, next));
  721.             }
  722.         i = next + 1;
  723.         imageNum++;
  724.     }
  725.     }
  726.  
  727.     /**
  728.      * Substitute an integer some number of times in a string, subject to
  729.      * parameter strings embedded in the string.
  730.      * Parameter strings:
  731.      *   %N - substitute the integer as is, with no padding.
  732.      *   %<digit>, for example %5 - substitute the integer left-padded with
  733.      *        zeros to <digits> digits wide.
  734.      *   %% - substitute a '%' here.
  735.      * @param inStr the String to substitute within
  736.      * @param theInt the int to substitute, as a String.
  737.      */
  738.     String doSubst(String inStr, String theInt) {
  739.     String padStr = "0000000000";
  740.     int length = inStr.length();
  741.     StringBuffer result = new StringBuffer(length);
  742.     
  743.     for (int i = 0; i < length;) {
  744.         char ch = inStr.charAt(i);
  745.         if (ch == '%') {
  746.         i++;
  747.         if (i == length) {
  748.             result.append(ch);
  749.         } else {
  750.             ch = inStr.charAt(i);
  751.             if (ch == 'N' || ch == 'n') {
  752.             // just stick in the number, unmolested
  753.             result.append(theInt);
  754.             i++;
  755.             } else {
  756.             int pad;
  757.             if ((pad = Character.digit(ch, 10)) != -1) {
  758.                 // we've got a width value
  759.                 String numStr = theInt;
  760.                 String scr = padStr+numStr;
  761.                 result.append(scr.substring(scr.length() - pad));
  762.                 i++;
  763.             } else {
  764.                 result.append(ch);
  765.                 i++;
  766.             }
  767.             }
  768.         }
  769.         } else {
  770.         result.append(ch);
  771.         i++;
  772.         }
  773.     }
  774.     return result.toString();
  775.     }    
  776.  
  777.     void startPlaying() {
  778.     if (soundTrack != null)
  779.         soundTrack.loop();
  780.     }
  781.  
  782.     void stopPlaying() {
  783.     if (soundTrack != null)
  784.         soundTrack.stop();
  785.     }
  786.  
  787.     public void paint(Graphics g) {
  788.         int xPos = 0;
  789.         int yPos = 0;
  790.         if ((frames.size() > 0) && tracker.checkID(ANIMATION_ID) && 
  791.                                                           (offScrGC != null)) {
  792.             AnimationFrame frame = (AnimationFrame) frames.get(currentFrame);
  793.             Image image = frame.image;
  794.             if (backgroundImage == null) {
  795.                 offScrGC.clearRect(0, 0, owner.appWidth, owner.appHeight);
  796.             }
  797.             else
  798.                 offScrGC.drawImage(backgroundImage, 0, 0, owner);
  799.             Point pos = null;
  800.             if (frame.position != null) {
  801.                 xPos = frame.position.x;
  802.                 yPos = frame.position.y;
  803.             }
  804.             if (backgroundColor != null) {
  805.                 offScrGC.setColor(backgroundColor);
  806.                 offScrGC.fillRect(0, 0, owner.appWidth, owner.appHeight);
  807.                 offScrGC.drawImage(image, xPos, yPos, backgroundColor, owner);
  808.             } else {
  809.                 offScrGC.drawImage(image, xPos, yPos, owner);
  810.             }
  811.             if (offScrImage != null)
  812.                 g.drawImage(offScrImage, 0, 0, owner);
  813.         }
  814.     }
  815. }
  816.  
  817. /**
  818.  * Instances of this class represent a single frame of an animation
  819.  * There can be an image, sound, and position associated with each frame
  820.  */
  821. class AnimationFrame extends Object {
  822.     static final String imageLabel = "image";
  823.     static final String soundLabel = "sound";
  824.  
  825.     URL imageLocation = null; // Directory or URL of this frames image
  826.     URL soundLocation = null; // Directory or URL of this frames sound
  827.     int duration;             // Duration time for this frame in milliseconds
  828.     AudioClip sound;          // Sound associated with this frame object
  829.     Image image;              // Image associated with this frame
  830.     Point position;           // Position of this frame
  831.  
  832. }
  833.  
  834. /**
  835.  * ParseException: signals a parameter parsing problem.
  836.  */
  837. class ParseException extends Exception {
  838.     ParseException(String s) {
  839.     super(s);
  840.     }
  841. }
  842.  
  843. /**
  844.  * DescriptionFrame: implements a pop-up "About" box.
  845.  */
  846. class DescriptionFrame extends Frame implements ActionListener {
  847.     static final int rows = 27;
  848.     static final int cols = 70;
  849.     TextArea info;
  850.     Button cancel;
  851.  
  852.     DescriptionFrame() {
  853.     super("Animator v1.10");
  854.     add("Center", info = new TextArea(rows, cols));
  855.     info.setEditable(false);
  856.     info.setBackground(Color.white);
  857.     Panel buttons = new Panel();
  858.     add("South", buttons);
  859.     buttons.add(cancel = new Button("Cancel"));
  860.     cancel.addActionListener(this);
  861.     pack();
  862.     }
  863.  
  864.     public void show() {
  865.     info.select(0,0);
  866.     super.show();
  867.     }
  868.  
  869.     void tell(String s) {
  870.     info.append(s);
  871.     }
  872.  
  873.     public void actionPerformed(ActionEvent e) {
  874.     setVisible(false);
  875.     }
  876. }
  877.