home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1997 October / PCO1097.ISO / FilesBBS / FREI / FSCROLL.EXE / SRC / FunScrollAnimate.java < prev    next >
Encoding:
Java Source  |  1997-09-02  |  39.2 KB  |  1,540 lines

  1. /*
  2.  * Copyright (c) 1995 by Jan Andersson, Torpa Konsult AB.
  3.  *
  4.  * Permission to use, copy, and distribute this software for
  5.  * NON-COMMERCIAL purposes and without fee is hereby granted
  6.  * provided that this copyright notice appears in all copies.
  7.  */
  8.  
  9. import java.awt.Color;
  10. import java.awt.Dimension;
  11. import java.awt.Font;
  12. import java.awt.FontMetrics;
  13. import java.awt.Graphics;
  14. import java.awt.MediaTracker;
  15. import java.awt.Image;
  16. import java.util.Vector;
  17. import java.util.StringTokenizer;
  18.  
  19. /**
  20.  * FunScroll Animate Text(s) and Image(s)
  21.  *
  22.  * @version 1.3 97/09/01
  23.  * @author  Jan Andersson (janne@torpa.se)
  24.  */
  25. public class FunScrollAnimate
  26. {
  27.                 // states:
  28.    static final int START = 0;    // start sequence
  29.    static final int SHOW = 1;    // show sequence
  30.    static final int END = 2;    // end sequence
  31.    static final int DONE = 3;    // done sequence
  32.    int state = START;        // animate state
  33.    FunScroll appl;        // FunScroll applet
  34.    FunScrollAttr attr;    // attributes
  35.  
  36.    String imageName;        // name of image
  37.    Image image;            // image
  38.    boolean imageLoaded = false; // true when image loaded
  39.    int imageWidth;        // image width
  40.    int imageHeight;        // image height
  41.  
  42.    Image bgImage;        // background image
  43.    String bgText;        // background text
  44.    boolean bgImageLoaded = false; // true when background image loaded
  45.    int bgImageWidth;        // background image width
  46.    int bgImageHeight;        // background image height
  47.  
  48.    MediaTracker mediaTracker = null; // media tracker
  49.    
  50.    FunScrollFade startFadeTrans = null;
  51.    FunScrollFade endFadeTrans = null;
  52.  
  53.    String unparsedText;        // unparsed text line
  54.    String[] lines;        // lines of text
  55.    protected int[] lineWidths;  // how wide each line is
  56.    int noOfLines = 1;        // number of lines
  57.    char chars[];        // the characters
  58.    int noOfChars;        // number of characters
  59.    int xPos[];            // the x positions
  60.    int yPos[];            // the y positions
  61.    boolean visible[];        // flags set to true if character visible
  62.    int typedCount = 0;        // used for "typed" characters
  63.    int currLineIndex = 0;        // used for "line-up" lines
  64.    int linePos = 0;        // used for "line-up" lines
  65.    int delayCount = 0;        // used to delay for a while
  66.    int margin;            // the margin
  67.    int offsetX;            // the x offset
  68.    int offsetY;            // the y offset
  69.    int width;            // the applet width
  70.    int height;            // the applet height
  71.    int scrollWidth;        // scrolling area width
  72.    int scrollHeight;        // scrolling area height
  73.    int textHeight;        // text height
  74.    int lineHeight;        // text line height
  75.    Color bg;            // background color
  76.    Color fg;            // foreground color
  77.    Color darkBg;        // dark background
  78.    Color lightDarkBg;        // lightdark background
  79.    Color brightBg;        // bright background
  80.    Color brightFg;        // bright foreground
  81.    
  82.    Font font;            // font
  83.    FontMetrics fontMetrics;    // font metrics
  84.    int ascent;            // font ascent
  85.    int descent;            // font descent
  86.    int leading;            // font leading
  87.    int maxWidth;        // max width
  88.    int sinDegree;        // used for sin-wave text
  89.    int xStart = -1;            // starting X pos
  90.    int yStart = -1;            // starting Y pos
  91.    int dx;            // x distance to move
  92.    int dy;            // y distance to move
  93.  
  94.    public FunScrollAnimate(FunScroll appl, String line,
  95.                Font font, Color fg, Color bg,
  96.                int dx, int dy, String delim)
  97.    {
  98.       this.appl = appl;
  99.       this.font = font;
  100.       this.fg = fg;
  101.       this.bg = bg;
  102.       this.dx = dx;
  103.       this.dy = dy;
  104.       this.unparsedText = line;
  105.       
  106.       // parse message line and init attributes
  107.       attr = new FunScrollAttr(line, delim);
  108.  
  109.       appl.dbg("Parsed Attributes:");
  110.       if (attr.type() == attr.TEXT)
  111.      appl.dbg("         msg:" + attr.param());
  112.       else
  113.      appl.dbg("       image:" + attr.param());
  114.       appl.dbg(" startScroll:" + attr.startScroll());
  115.       appl.dbg("   endScroll:" + attr.endScroll());
  116.       appl.dbg("   showDelay:" + attr.showDelay());
  117.       appl.dbg("    endDelay:" + attr.endDelay());
  118.       appl.dbg("       style:" + attr.style());
  119.       appl.dbg("   drawStyle:" + attr.drawStyle());
  120.       appl.dbg("      color:" + attr.color());
  121.       appl.dbg("    bgImage:" + attr.bgImage());
  122.       appl.dbg("     bgText:" + attr.bgText());
  123.       appl.dbg("   bgOffsetX:" + attr.bgOffsetX());
  124.       appl.dbg("   bgOffsetY:" + attr.bgOffsetY());
  125.       appl.dbg("     offsetX:" + attr.offsetX());
  126.       appl.dbg("     offsetY:" + attr.offsetY());
  127.       appl.dbg("dy:" + dy + " dx:" + dx);
  128.       
  129.       // get color attribute (if specified)
  130.       if (attr.color() != null) 
  131.      this.fg = appl.readColor(attr.color(), fg);
  132.       appl.dbg("      color:" + fg);
  133.       
  134.       // Init font stuff (we probably need this)
  135.       fontMetrics = appl.getFontMetrics(font);
  136.       ascent = fontMetrics.getAscent();
  137.       descent = fontMetrics.getDescent();
  138.       leading = fontMetrics.getLeading();
  139.       lineHeight = fontMetrics.getHeight();
  140.       
  141.       if (attr.type() == attr.TEXT)
  142.      initText();
  143.       else
  144.      initImage();
  145.    }
  146.    
  147.    
  148.    /**
  149.     * Reset animation
  150.     */
  151.    void reset()
  152.    {
  153.       int offsetX = attr.offsetX();
  154.       int offsetY = attr.offsetY();
  155.       int width = appl.size().width;
  156.       int height = appl.size().height;
  157.       int margin = appl.getMargin();
  158.       
  159.       if (this.offsetX != offsetX ||
  160.       this.offsetY != offsetY ||
  161.       this.width != width ||
  162.           this.height != height) {
  163.      startFadeTrans = null;
  164.      endFadeTrans = null;
  165.       }
  166.       this.offsetX = offsetX;
  167.       this.offsetY = offsetY;
  168.       this.width = width;
  169.       this.height = height;
  170.       this.margin = margin;
  171.       this.scrollWidth = width - 2*margin - offsetX;
  172.       this.scrollHeight = height - 2*margin - offsetY;
  173.  
  174.       if (startFadeTrans != null)
  175.      startFadeTrans.resetFrameIndex();
  176.       if (endFadeTrans != null)
  177.      endFadeTrans.resetFrameIndex();
  178.       
  179.       if (attr.type() == attr.TEXT)
  180.      resetText();
  181.       else
  182.      resetImage();
  183.       
  184.       // re-init background image
  185.       initBgImage();
  186.       
  187.    }
  188.  
  189.    
  190.  
  191.    /**  
  192.     * Update. I.e move and paint.
  193.     */
  194.    boolean update(Graphics g) 
  195.    {
  196.       // reset font and foreground
  197.       g.setFont(font);
  198.       g.setColor(fg);
  199.  
  200.       // Animate
  201.       if (attr.type() == attr.TEXT) 
  202.      animateText();
  203.       else
  204.      animateImage();
  205.       
  206.       if (state == DONE && delayCount <= 0)
  207.      return true;        // we are done!
  208.  
  209.       // Draw background image if defined
  210.       if (bgImage != null) 
  211.      paintBgImage(g);
  212.  
  213.       // set new clip rext if offsetX or offsetY specified
  214.       if (offsetX != 0 || offsetY != 0)
  215.      g.clipRect(margin + offsetX,
  216.             margin + offsetY,
  217.             width - (2*margin) - offsetX,
  218.             height- (2*margin) - offsetY);
  219.       
  220.       // paint text
  221.       if (attr.type() == attr.TEXT) 
  222.      paintText(g);
  223.       else 
  224.      paintImage(g);
  225.       return false;
  226.    }
  227.  
  228.    /**
  229.     * Init background image (if specified)
  230.     */
  231.    void initBgImage()
  232.    {
  233.       if (bgImage != null)
  234.      return;        // already initiated...
  235.       
  236.       bgImage = null;
  237.       if (attr.bgImage() == null) {
  238.      // no background image specified; check for background text...
  239.      if (attr.bgText() != null) {
  240.         // background text specified; create bgImage
  241.         bgText = attr.bgText();
  242.         bgImage = imageCreateEmpty(getBgTextSize().width,
  243.                        getBgTextSize().height);
  244.      }
  245.       }
  246.       else {
  247.      // get background image
  248.      bgImage = appl.getImage(appl.getCodeBase(), attr.bgImage());
  249.       }
  250.       
  251.       if (bgImage != null) {
  252.      // we have a background image specified; start loading
  253.      imageLoad(bgImage, 0);
  254.      bgImageWidth = 0;
  255.      bgImageHeight = 0;
  256.      bgImageLoaded = false;
  257.      checkBgImage();
  258.       }
  259.    }
  260.  
  261.    /**
  262.     * Check if image loaded
  263.     */
  264.    boolean checkBgImage()
  265.    {
  266.       if (!imageCheck(0))
  267.      return false;
  268.       FunScroll.dbg("checkBgImage: loaded!");
  269.       bgImageLoaded = true;
  270.       bgImageWidth = bgImage.getWidth(null);
  271.       bgImageHeight = bgImage.getHeight(null);
  272.  
  273.       if (bgText != null) 
  274.      // write background text to background image
  275.      imageDrawString(bgImage, bgText, 0, 0);
  276.       
  277.       return true;        // loaded
  278.    }
  279.  
  280.    Dimension getBgTextSize()
  281.    {
  282.       int bgTextWidth = fontMetrics.stringWidth(bgText);
  283.       int bgTextHeight = lineHeight;
  284.       return new Dimension(bgTextWidth, bgTextHeight);
  285.    }
  286.  
  287.    void paintBgImage(Graphics g)
  288.    {
  289.       if (!bgImageLoaded)
  290.      checkBgImage();
  291.       if (bgImageLoaded) {
  292.      int x = (width-bgImageWidth)/2;
  293.      int y = (height-bgImageHeight)/2;
  294.      if (attr.bgOffsetX() != 0)
  295.         x = attr.bgOffsetX();
  296.      if (attr.bgOffsetY() != 0)
  297.         y = attr.bgOffsetY();
  298.      g.drawImage(bgImage, x, y, null);
  299.       }
  300.    }
  301.  
  302.    // -----------------------------------------------------------------
  303.    // Text related functions
  304.  
  305.    /**
  306.     * Init text attributes
  307.     */
  308.    void initText()
  309.    {
  310.       // init lines of text
  311.       lines = getLines(attr.param());
  312.       noOfLines = lines.length;
  313.  
  314.       // init line widths
  315.       lineWidths = getLineWidths(lines);
  316.  
  317.       // cound number of characters
  318.       noOfChars = 0;
  319.       for (int i=0; i < noOfLines; i++) 
  320.      noOfChars += lines[i].length();
  321.  
  322.       // init character arrays
  323.       chars = new char[noOfChars];
  324.       xPos = new int[noOfChars];
  325.       yPos = new int[noOfChars];
  326.       visible = new boolean[noOfChars];
  327.       int charIndex = 0;
  328.       int currXPos = 0;
  329.       int currYPos = ascent;
  330.       textHeight = 0;
  331.       maxWidth = 0;
  332.       for (int i=0; i < noOfLines; i++) {
  333.      int lineLength = lines[i].length();
  334.      int lineStartIndex = charIndex;
  335.      for (int j=0; j<lineLength; j++) {
  336.         chars[charIndex] = lines[i].charAt(j);
  337.         xPos[charIndex] = (j==0) ? 0 :
  338.            fontMetrics.charsWidth(
  339.           chars, lineStartIndex, j);
  340.         yPos[charIndex] = currYPos;
  341.         charIndex++;
  342.      }
  343.      maxWidth = Math.max(maxWidth, xPos[charIndex-1]);
  344.      textHeight += lineHeight;
  345.      currYPos += lineHeight;
  346.       }
  347.       
  348.       if (attr.style() == FunScrollAttr.NERVOUS ||
  349.       attr.style() == FunScrollAttr.SINEWAVE)
  350.      // need some extra space here!
  351.      textHeight += 4;
  352.    }
  353.  
  354.    /**
  355.     * Parse text line(s)
  356.     */
  357.    String[] parseLines(String msg)
  358.    {
  359.       Vector linesOftext = new Vector();
  360.       StringTokenizer st = new StringTokenizer(msg, "\\n\n", true);
  361.       boolean escape = false;
  362.       boolean newLine = false;
  363.       String line = new String();
  364.       String token = null;
  365.       while (st.hasMoreTokens()) {
  366.      token = st.nextToken();
  367.      if (token.equals("\\")) 
  368.         // escaped characted; wait for next character
  369.         escape = true;
  370.      else if (token.equals("n") && escape) {
  371.         // got "\n" - line break
  372.         newLine = true;
  373.         escape = false;
  374.      }
  375.      else if (token.equals("\n")) 
  376.         newLine = true;
  377.      else {
  378.         if (escape)
  379.            line += "\\";
  380.         line += token;
  381.      }
  382.      if (newLine) {
  383.         linesOftext.addElement(line);
  384.         line = new String();
  385.         token = null;
  386.         newLine = false;
  387.      }
  388.       }
  389.       if (line.length() > 0) {
  390.      linesOftext.addElement(line);
  391.       }
  392.       int noOfLines = linesOftext.size();
  393.       String[] lines = new String[noOfLines];
  394.       for (int i=0; i < noOfLines; i++) 
  395.          lines[i] = (String)linesOftext.elementAt(i);
  396.       return lines;
  397.    }
  398.  
  399.    /**
  400.     * Reset array of x positions 
  401.     */
  402.    void resetTextX()
  403.    {
  404.       int currXPos = 0;
  405.       int currYPos = ascent;
  406.       int charIndex = 0;
  407.       for (int i=0; i < noOfLines; i++) {
  408.      int lineLength = lines[i].length();
  409.      int lineStartIndex = charIndex;
  410.      for (int j=0; j<lineLength; j++) {
  411.         xPos[charIndex] = (j==0) ? 0 :
  412.            fontMetrics.charsWidth(
  413.           chars, lineStartIndex, j);
  414.         charIndex++;
  415.      }
  416.       }
  417.    }
  418.  
  419.    /**
  420.     * Reset width and height
  421.     */
  422.    void resetText()
  423.    {
  424.       int scroll = attr.startScroll();
  425.       switch (scroll) {
  426.       case FunScrollAttr.NONE:
  427.       case FunScrollAttr.FADE:
  428.       case FunScrollAttr.TYPED:
  429.      xStart = (scrollWidth-maxWidth)/2;
  430.      yStart = (scrollHeight-textHeight)/2;
  431.      break;
  432.       case FunScrollAttr.LEFT:
  433.      xStart = scrollWidth-dx-margin;
  434.      yStart = (scrollHeight-textHeight)/2;
  435.      break;
  436.       case FunScrollAttr.RIGHT:
  437.      xStart = dx+margin-maxWidth;
  438.      yStart = (scrollHeight-textHeight)/2;
  439.      break;
  440.       case FunScrollAttr.UP:
  441.      xStart = (scrollWidth-maxWidth)/2;
  442.      yStart = scrollHeight-descent-margin;
  443.      break;
  444.       case FunScrollAttr.UP_LINE:
  445.      xStart = (scrollWidth-maxWidth)/2;
  446.      yStart = scrollHeight-descent-margin;
  447.      break;
  448.       case FunScrollAttr.DOWN:
  449.      xStart = (scrollWidth-maxWidth)/2;
  450.      yStart = 0-textHeight+margin;
  451.      break;
  452.       }
  453.  
  454.       // adjust for margin and offsets
  455.       xStart += offsetX;
  456.       yStart += offsetY;
  457.       linePos = yStart;
  458.      
  459.       // adjust for margin
  460.       //width -= 2*margin;
  461.       //height -= 2*margin;
  462.  
  463.       // Reset array of x positions
  464.       resetTextX();
  465.       
  466.       // reset typed and characters and lines count
  467.       currLineIndex = 0;
  468.       typedCount = 0;
  469.       
  470.       // reset state
  471.       state = START;
  472.       FunScroll.dbg("State: START");
  473.  
  474.    }
  475.  
  476.    public String getUnparsedTextLine()
  477.    {
  478.       return unparsedText;
  479.    }
  480.    
  481.    /**
  482.     * Animate text 
  483.     */
  484.    void animateText()
  485.    {
  486.       boolean switchState = false;
  487.       int scroll = FunScrollAttr.NONE;
  488.       switch (state) {
  489.       case START:
  490.      // start sequence
  491.      scroll = attr.startScroll();
  492.      if (scroll == FunScrollAttr.NONE) {
  493.         // no animation;     just switch state
  494.         switchState = true;
  495.      }
  496.      else if (scroll == FunScrollAttr.FADE) {
  497.         if (startFadeTrans != null && startFadeTrans.done()) {
  498.            // fade frames all displayed; switch state
  499.            switchState = true;
  500.         }
  501.      }
  502.      else {
  503.         // some other kind of animation; check if all characters displ.
  504.         if (textDisplayed(scroll)) {
  505.            // yupp; switch state
  506.            switchState = true;
  507.            if (scroll == FunScrollAttr.UP_LINE)
  508.           // reset yStart after UP_LINE animation
  509.           yStart = margin + offsetY + leading;
  510.         }
  511.      }
  512.      
  513.      if (!switchState) {
  514.         // just move text (scroll)
  515.         moveText(scroll);
  516.         updateVisible(scroll);
  517.         break;
  518.      }
  519.      
  520.      // switch state
  521.      updateVisible(scroll);
  522.      state = SHOW;
  523.      FunScroll.dbg("State: SHOW");
  524.      delayCount = attr.showDelay();
  525.      // fall trough!
  526.      
  527.       case SHOW:
  528.      // show sequence
  529.      if (--delayCount >= 0) {
  530.         // delay. I.e break out
  531.         break;
  532.      }
  533.      
  534.      // switch state
  535.      state = END;
  536.      FunScroll.dbg("State: END");
  537.      // fall trough!
  538.      
  539.       case END:
  540.      // end sequence
  541.      if (attr.endScroll() == attr.FADE) {
  542.         if (endFadeTrans != null && endFadeTrans.done()) {
  543.            // fade frames all displayed
  544.            state = DONE;
  545.            FunScroll.dbg("State: DONE");
  546.            delayCount = attr.endDelay();
  547.            // fall trough!
  548.         }
  549.         else
  550.            break;
  551.      }
  552.      
  553.      // check if all characters still visible
  554.      else if (updateVisible(attr.endScroll()) == 0 ||
  555.           attr.endScroll() == FunScrollAttr.NONE) {
  556.         // none visible or no end animation; switch state
  557.         state = DONE;
  558.         FunScroll.dbg("State: DONE");
  559.         delayCount = attr.endDelay();
  560.         return;
  561.      }
  562.      else {
  563.         moveText(attr.endScroll());
  564.      }
  565.      break;
  566.      
  567.       case DONE:
  568.      // done sequence; just delay
  569.      delayCount--;
  570.      break;
  571.       }
  572.    }
  573.  
  574.    /**
  575.     * Return true if (all) text is displayed
  576.     */
  577.    boolean textDisplayed(int scroll)
  578.    {
  579.       switch (scroll) {
  580.       case FunScrollAttr.LEFT:
  581.      // scrolling left
  582.      if (maxWidth > scrollWidth) {
  583.         // text is wider that applet width
  584.         if (maxWidth+xStart < scrollWidth-4*dx)
  585.            return true;
  586.      }
  587.      else {
  588.         int appletMidPoint = scrollWidth/2;
  589.         int textMidPoint = xStart+maxWidth/2;
  590.         if (textMidPoint <= appletMidPoint)
  591.            return true;
  592.      }
  593.      break;
  594.      
  595.       case FunScrollAttr.RIGHT:
  596.      // scrolling right
  597.      if (maxWidth > scrollWidth) {
  598.         // text is wider that applet width
  599.         if (xPos[0]+xStart > 4*dx)
  600.            return true;
  601.      }
  602.      else {
  603.         int appletMidPoint = scrollWidth/2;
  604.         int textMidPoint = xStart+maxWidth/2;
  605.         if (textMidPoint >= appletMidPoint)
  606.            return true;
  607.      }
  608.      break;
  609.      
  610.       case FunScrollAttr.UP:
  611.      // scrolling up
  612.      if (yStart <= (scrollHeight-textHeight)/2 + margin)
  613.         return true;
  614.      break;
  615.  
  616.       case FunScrollAttr.UP_LINE:
  617.      // scrolling up (line-by-line)
  618.      if (currLineIndex >= noOfLines)
  619.         return true;
  620.      if (currLineIndex == noOfLines-1) {
  621.         int stopLinePos = margin + offsetY + ascent + leading +
  622.            (lineHeight*currLineIndex);
  623.         if (linePos <= stopLinePos)
  624.            return true;
  625.      }
  626.      break;
  627.      
  628.       case FunScrollAttr.DOWN:
  629.      // scrolling down
  630.      if (yStart >= (scrollHeight-textHeight)/2 + margin) 
  631.         return true;
  632.      break;
  633.      
  634.       case FunScrollAttr.TYPED:
  635.      // "typed" characters
  636.      if (typedCount > noOfChars)
  637.         return true;
  638.      break;
  639.       }
  640.       return false;
  641.    }
  642.    
  643.    /**
  644.     * update array with flags if characters are visible. Return
  645.     * number of visible characters.
  646.     */
  647.    int updateVisible(int scroll)
  648.    {
  649.       int visibleCount = 0;
  650.  
  651.       boolean checkTypedCount = false;
  652.       if (state == START && scroll == attr.TYPED)
  653.      checkTypedCount = true;
  654.       boolean checkLineCount = false;
  655.       int maxVisibleIndex = 0;
  656.       if (state == START && scroll == attr.UP_LINE) {
  657.      checkLineCount = true;
  658.      for (int i = 0; i <= currLineIndex && i < noOfLines; i++)
  659.         maxVisibleIndex += lines[i].length();
  660.       }
  661.       
  662.       for (int i = 0; i < noOfChars; i++) {
  663.      if (checkTypedCount) 
  664.         visible[i] = (i <= typedCount);
  665.      else if (checkLineCount) 
  666.         visible[i] = (i < maxVisibleIndex);
  667.      else
  668.         visible[i] = (xPos[i]+xStart > margin &&
  669.               xPos[i]+xStart < width-margin &&
  670.               yPos[i]+yStart+lineHeight > margin  &&
  671.               yPos[i]+yStart-lineHeight < height-margin);
  672.      if (visible[i])
  673.         visibleCount++;
  674.       }
  675.       // special treatment of explode animation
  676.       if (scroll == FunScrollAttr.EXPLODE) {
  677.      // if only 4 or less chars visible (per line) consider this as done
  678.      if (visibleCount <= (noOfLines*4))
  679.         visibleCount = 0;
  680.       }
  681.       return visibleCount;
  682.    }
  683.       
  684.    void moveText(int scroll)
  685.    {
  686.       switch (scroll) {
  687.       case FunScrollAttr.LEFT:
  688.      xStart -= dx;
  689.      break;
  690.       case FunScrollAttr.RIGHT:
  691.      xStart += dx;
  692.      break;
  693.       case FunScrollAttr.UP:
  694.      yStart -= dy;
  695.      break;
  696.       case FunScrollAttr.UP_LINE:
  697.      if (currLineIndex < noOfLines) {
  698.         int stopLinePos = margin + offsetY + ascent + leading +
  699.            (lineHeight*currLineIndex);
  700.         if (linePos <= stopLinePos) {
  701.            // next line
  702.            currLineIndex++;
  703.            //linePos = scrollHeight-descent-margin+offsetY;
  704.            //linePos = scrollHeight+margin+offsetY;
  705.            linePos = yStart;
  706.         }
  707.         else
  708.            // move current line
  709.            linePos -= dy;
  710.      }
  711.      break;
  712.       case FunScrollAttr.DOWN:
  713.      yStart += dy;
  714.      break;
  715.       case FunScrollAttr.TYPED:
  716.      typedCount++;
  717.      break;
  718.       case FunScrollAttr.EXPLODE:
  719.      moveExplodeText();
  720.      break;
  721.       }
  722.       
  723.    }
  724.  
  725.    /**
  726.     * Move exploding text
  727.     */
  728.    void moveExplodeText() {
  729.       int visibleChars = updateVisible(attr.endScroll());
  730.       
  731.       // noOfChars
  732.       int mid = noOfChars/2;
  733.       int maxDist = maxWidth/20;
  734.       double explodeFactor = 1.1;
  735.       if (visibleChars/noOfChars < 0.2)
  736.      explodeFactor = 2;
  737.       else if(visibleChars/noOfChars < 0.5)
  738.      explodeFactor = 1.5;
  739.       
  740.       for (int i = mid-1; i >= 0; i--) {
  741.      // move to the left
  742.      int moveDist = (int) ((mid - i) * explodeFactor);
  743.      moveDist = Math.min(maxDist, moveDist);
  744.      xPos[i] -= moveDist;
  745.       }
  746.       for (int i = mid; i < noOfChars; i++) {
  747.      // move to the right
  748.      int moveDist = (int) ((i - mid + 1 ) * explodeFactor);
  749.      if (moveDist == 0)
  750.         moveDist = 1;
  751.      moveDist = Math.min(maxDist, moveDist);
  752.      xPos[i] += moveDist;
  753.       }
  754.    }
  755.    
  756.    /**
  757.     * Paint characters
  758.     */
  759.    void paintText(Graphics g)
  760.    {
  761.       if (state == END || state == DONE) {
  762.      if (state == END && attr.endScroll() == FunScrollAttr.EXPLODE) {
  763.         paintExplode(g);
  764.         return;
  765.      }
  766.      else if (attr.endScroll() == FunScrollAttr.FADE) {
  767.         if (endFadeTrans == null) {
  768.            endFadeTrans = new FunScrollFade(
  769.           appl, width + margin, height + margin);
  770.            endFadeTrans.initToImage(appl.getOffImage());
  771.         }
  772.         else {
  773.            endFadeTrans.drawFrame(g);
  774.            return;
  775.         }
  776.      }
  777.       }
  778.  
  779.       if (state == START && attr.startScroll() == FunScrollAttr.FADE) {
  780.      if (startFadeTrans == null) {
  781.         startFadeTrans = new FunScrollFade(
  782.            appl, width + margin, height + margin);
  783.         startFadeTrans.initFromImage(appl.getOffImage());
  784.         // create image to transform to...
  785.         Image image = imageCreateEmpty(width + margin, height + margin);
  786.         Graphics gr = image.getGraphics();
  787.         Image fromImage = startFadeTrans.getFromImage();
  788.         gr.drawImage(fromImage, 0, 0, null);
  789.         gr.setFont(font);
  790.         gr.setColor(fg);
  791.         paintNormal(gr);
  792.         startFadeTrans.initToImage(image);
  793.         gr.dispose();
  794.         gr = null;
  795.      }
  796.      startFadeTrans.drawFrame(g);
  797.      return;
  798.       }
  799.  
  800.       // set foreground color
  801.       g.setColor(fg);
  802.       
  803.       switch (attr.style()) {
  804.       case FunScrollAttr.SINEWAVE:
  805.      paintSineWave(g);
  806.      break;
  807.       case FunScrollAttr.NERVOUS:
  808.      paintNervous(g);
  809.      break;
  810.       default:
  811.      paintNormal(g);
  812.      break;
  813.      
  814.       }
  815.  
  816.       if (state == END && attr.endScroll() == FunScrollAttr.FADE) {
  817.      if (endFadeTrans != null) 
  818.         endFadeTrans.initFromImage(appl.getOffImage());
  819.       }
  820.    }
  821.    
  822.    /**
  823.     * Paint "exploding" text line
  824.     */
  825.    void paintExplode(Graphics g) {
  826.       for (int i = 0; i < noOfChars; i++) {
  827.      if (visible[i]) 
  828.         drawNormalChar(g, i);
  829.       }
  830.    }
  831.  
  832.    /**
  833.     * Paint normal text line
  834.     */
  835.    void paintNormal(Graphics g) {
  836.       switch (attr.drawStyle()) {
  837.       case attr.ENGRAVE:
  838.       case attr.EMBOSS:
  839.      // pain emboss or engrave line
  840.      paintEmbossEngrave(g);
  841.      break;
  842.       case attr.SHADOW:
  843.      // pain shadowed line
  844.      paintShadow(g);
  845.      break;
  846.       case attr.NONE:
  847.      // draw normal line(s)
  848.      for (int i=0; i < noOfLines; i++) {
  849.         drawAlignedString(g, i,
  850.              xStart, yStart + ascent + (lineHeight*i));
  851.      }
  852.      break;
  853.       }
  854.    }
  855.  
  856.    /**
  857.     * Paint emboss/engrave text line
  858.     */
  859.    void paintEmbossEngrave(Graphics g) {
  860.       // init colors (first time)
  861.       if (darkBg == null) {
  862.      darkBg = FunScroll.darker(bg, 0.5);
  863.      lightDarkBg = FunScroll.darker(bg, 0.5 - (0.5/2));
  864.      brightBg = FunScroll.brighter(bg, 0.5);
  865.       }
  866.       
  867.       int drawStyle = attr.drawStyle();
  868.       Color upperLeftColor = (drawStyle == attr.ENGRAVE) ? darkBg : brightBg;
  869.       Color upperRightColor = (drawStyle == attr.ENGRAVE) ? brightBg : darkBg;
  870.       Color mainColor = (drawStyle == attr.ENGRAVE) ? lightDarkBg : bg;
  871.       int depth = 1;        // hardkoded ;-(
  872.       
  873.       for (int i=0; i < noOfLines; i++) {
  874.      drawAlignedString(g, i,
  875.               xStart, yStart + ascent + (lineHeight*i));
  876.  
  877.      // upper left edge
  878.      g.setColor(upperLeftColor);
  879.      drawAlignedString(g, i,
  880.               xStart,
  881.               yStart + ascent + (lineHeight*i) - depth);
  882.      
  883.      // lower right edge
  884.      g.setColor(upperRightColor);
  885.      drawAlignedString(g, i,
  886.               xStart + depth*2,
  887.               yStart + ascent + (lineHeight*i) + depth);
  888.      
  889.      // main body of the character
  890.      g.setColor(mainColor);
  891.      drawAlignedString(g, i,
  892.               xStart + depth,
  893.               yStart + ascent + (lineHeight*i));
  894.       }
  895.    }
  896.  
  897.    /**
  898.     * Paint emboss/engrave text line
  899.     */
  900.    void paintShadow(Graphics g) {
  901.       int shadowOffset = 4;
  902.       if (brightFg == null) 
  903.      brightFg = FunScroll.brighter(fg, 0.75);
  904.       for (int i=0; i < noOfLines; i++) {
  905.      g.setColor(brightFg);
  906.      drawAlignedString(g, i,
  907.               xStart + shadowOffset,
  908.               yStart + ascent + (lineHeight*i) + shadowOffset);
  909.      g.setColor(fg);
  910.      drawAlignedString(g, i,
  911.                xStart,
  912.                yStart + ascent + (lineHeight*i));
  913.       }
  914.    }
  915.  
  916.    /**
  917.     * draw aligned string
  918.     */
  919.    void drawAlignedString(Graphics g, int index, int x, int y)
  920.    {
  921.       switch(attr.align()) {
  922.       case attr.LEFT:
  923.      break;
  924.       case attr.RIGHT:
  925.      x = width - x - lineWidths[index];
  926.      break;
  927.       case attr.CENTER:
  928.      x = x + (maxWidth - lineWidths[index])/2;
  929.      break;
  930.       }
  931.       
  932.       if (state == START && attr.startScroll() == attr.TYPED)
  933.      drawTypedString(g, index, x, y);
  934.       else if (state == START && attr.startScroll() == attr.UP_LINE)
  935.      drawLineByLine(g, index, x, y);
  936.       else 
  937.      g.drawString(lines[index], x, y);
  938.    }
  939.  
  940.    /**
  941.     * draw "typed" chars of line
  942.     */
  943.    void drawTypedString(Graphics g, int index, int x, int y)
  944.    {
  945.       // calc. number of chars in prev. lines and current line
  946.       int noOfCharsPrev = 0;
  947.       for (int i=0; i<index; i++)
  948.      noOfCharsPrev += lines[i].length();
  949.       int noOfCharsCurr = lines[index].length();
  950.       
  951.       if (noOfCharsPrev + noOfCharsCurr < typedCount) {
  952.      // just print line
  953.      g.drawString(lines[index], x, y);
  954.       }
  955.       else {
  956.      // there are chars in this line that should be "typed"
  957.      int typedChars = typedCount - noOfCharsPrev;
  958.      if (typedChars > 0) {
  959.         String currLine = lines[index].substring(0, typedChars);
  960.         g.drawString(currLine, x, y);
  961.      }
  962.       }
  963.    }
  964.    /**
  965.     * draw "line by line"
  966.     */
  967.    void drawLineByLine(Graphics g, int index, int x, int y)
  968.    {
  969.       if (index < currLineIndex) {
  970.      // just print line
  971.      int lineY = margin + offsetY + ascent + leading +
  972.         (lineHeight*index);
  973.      g.drawString(lines[index], x, lineY);
  974.       }
  975.       else if (index == currLineIndex) {
  976.      // draw at line pos
  977.      g.drawString(lines[index], x, linePos);
  978.      
  979.       }
  980.    }
  981.    
  982.    /**
  983.     * Paint sine-wave text line
  984.     */
  985.    void paintSineWave(Graphics g) {
  986.       int currYPos = (noOfChars > 0) ? yPos[0] : 0;
  987.       int degree = sinDegree;
  988.       for (int i = noOfChars-1; i >= 0; i--) {
  989.      if (currYPos != yPos[i]) {
  990.         // new line
  991.         currYPos = yPos[i];
  992.         degree = sinDegree;
  993.      }
  994.      if (visible[i]) {
  995.         // draw character
  996.         int sinHeight = lineHeight/3;
  997.         int y = (int) (Math.sin(degree*3.1414/180) * sinHeight);
  998.         drawChar(g, i, xPos[i]+xStart, yPos[i]+yStart+y);
  999.      }
  1000.      degree -= 15;
  1001.      if (degree <= 0)
  1002.         degree = 360;
  1003.       }
  1004.       sinDegree -= 15;
  1005.       if (sinDegree <= 0)
  1006.      sinDegree = 360;
  1007.    }
  1008.  
  1009.    /**
  1010.     * Paint nervous text line
  1011.     */
  1012.    void paintNervous(Graphics g) {
  1013.       for (int i = 0; i < noOfChars; i++) {
  1014.      if (visible[i]) 
  1015.         drawNervousChar(g, i);
  1016.       }
  1017.    }
  1018.    
  1019.    /**
  1020.     * Draw nervous character
  1021.     */
  1022.    void drawNervousChar(Graphics g, int index) 
  1023.    {
  1024.       int x = (int)(Math.random() * 2) + xPos[index];
  1025.       int y = (int)(Math.random() * 4) + yPos[index];
  1026.       drawChar(g, index, x+xStart, y+yStart);
  1027.    }
  1028.  
  1029.    /**
  1030.     * Draw normal character
  1031.     */
  1032.    void drawNormalChar(Graphics g, int index) 
  1033.    {
  1034.       drawChar(g, index, xPos[index]+xStart, yPos[index]+yStart);
  1035.    }
  1036.  
  1037.    /**
  1038.     * Draw character
  1039.     */
  1040.    void drawChar(Graphics g, int index, int x, int y)
  1041.    {
  1042.       if (state == START && attr.startScroll() == attr.UP_LINE) {
  1043.      // adjust y pos to handle movement of line by line
  1044.      int startIndexOfCurrentLine = 0;
  1045.      int endIndexOfCurrentLine = 0;
  1046.      for (int i = 0; i <= currLineIndex && i < noOfLines; i++) {
  1047.         endIndexOfCurrentLine += lines[i].length();
  1048.         if (i < currLineIndex)
  1049.            startIndexOfCurrentLine = endIndexOfCurrentLine;
  1050.      }
  1051.      if (index >= startIndexOfCurrentLine &&
  1052.          index < endIndexOfCurrentLine) {
  1053.         //y = (y - yStart) + linePos;
  1054.         int yPos = (y - yStart);
  1055.         y = yPos + (linePos - yPos);
  1056.      }
  1057.       }
  1058.      
  1059.       int drawStyle = attr.drawStyle();
  1060.       if (drawStyle == attr.NONE || drawStyle == attr.SHADOW) {
  1061.      if (drawStyle == attr.SHADOW) {
  1062.         int shadowOffset = 4;
  1063.         if (brightFg == null) 
  1064.            brightFg = FunScroll.brighter(fg, 0.75);
  1065.         g.setColor(brightFg);
  1066.         g.drawChars(chars, index, 1,
  1067.             x + shadowOffset, y + shadowOffset);
  1068.      }
  1069.      
  1070.      // default draw style
  1071.      g.setColor(fg);
  1072.      g.drawChars(chars, index, 1, x, y);
  1073.      return;
  1074.       }
  1075.  
  1076.       // draw style is ENGRAVE or EMBOSS
  1077.       
  1078.       // init colors (first time)
  1079.       if (darkBg == null) {
  1080.      darkBg = FunScroll.darker(bg, 0.5);
  1081.      lightDarkBg = FunScroll.darker(bg, 0.5 - (0.5/2));
  1082.      brightBg = FunScroll.brighter(bg, 0.5);
  1083.       }
  1084.       
  1085.       int depth = 1;        // hardkoded ;-(
  1086.       Color upperLeftColor =
  1087.      (drawStyle == attr.ENGRAVE) ? darkBg : brightBg;
  1088.       Color upperRightColor =
  1089.      (drawStyle == attr.ENGRAVE) ? brightBg : darkBg;
  1090.       Color mainColor =
  1091.      (drawStyle == attr.ENGRAVE) ? lightDarkBg : bg;
  1092.       
  1093.       // upper left edge
  1094.       g.setColor(upperLeftColor);
  1095.       g.drawChars(chars, index, 1, x, y-depth);
  1096.       
  1097.       // lower right edge
  1098.       g.setColor(upperRightColor);
  1099.       g.drawChars(chars, index, 1, x+depth*2, y+depth);
  1100.       
  1101.       // main body of the character
  1102.       g.setColor(mainColor);
  1103.       g.drawChars(chars, index, 1, x+depth, y);
  1104.    }
  1105.    
  1106.    // -----------------------------------------------------------------
  1107.    // Image related functions
  1108.  
  1109.    /**
  1110.     * Init image attributes
  1111.     */
  1112.    void initImage()
  1113.    {
  1114.       String imageName = attr.param();
  1115.       FunScroll.dbg("get image:" + imageName);
  1116.       image = appl.getImage(appl.getCodeBase(), imageName);
  1117.       imageLoad(image, 1);
  1118.       imageWidth = 0;
  1119.       imageHeight = 0;
  1120.       imageLoaded = false;
  1121.       checkImage();        // to start loading
  1122.    }
  1123.  
  1124.    /**
  1125.     * Check if image loaded
  1126.     */
  1127.    boolean checkImage()
  1128.    {
  1129.       if (!imageCheck(1))
  1130.      return false;        //
  1131.  
  1132.       FunScroll.dbg("checkImage: loaded!");
  1133.       imageLoaded = true;
  1134.       
  1135.       imageWidth = image.getWidth(null);
  1136.       imageHeight = image.getHeight(null);
  1137.  
  1138.       return true;        // loaded
  1139.    }
  1140.    
  1141.    /**
  1142.     * Reset width and height
  1143.     */
  1144.    void resetImage()
  1145.    {
  1146.       if (!imageLoaded)
  1147.      return;
  1148.       
  1149.       int scroll = attr.startScroll();
  1150.       switch (scroll) {
  1151.       case FunScrollAttr.NONE:
  1152.       case FunScrollAttr.FADE:
  1153.      xStart = (scrollWidth-imageWidth)/2;
  1154.      yStart = (scrollHeight-imageHeight)/2;
  1155.      break;
  1156.       case FunScrollAttr.LEFT:
  1157.      xStart = scrollWidth-dx-margin;
  1158.      yStart = (scrollHeight-imageHeight)/2;
  1159.      break;
  1160.       case FunScrollAttr.RIGHT:
  1161.      xStart = dx+margin-imageWidth;
  1162.      yStart = (scrollHeight-imageHeight)/2;
  1163.      break;
  1164.       case FunScrollAttr.UP:
  1165.      xStart = (scrollWidth-imageWidth)/2;
  1166.      yStart = scrollHeight-margin;
  1167.      break;
  1168.       case FunScrollAttr.DOWN:
  1169.      xStart = (scrollWidth-imageWidth)/2;
  1170.      yStart = 0-imageHeight+margin;
  1171.      break;
  1172.       }
  1173.  
  1174.       // adjust for margin and offsets
  1175.       xStart += offsetX;
  1176.       yStart += offsetY;
  1177.       
  1178.       // adjust for margin
  1179.       //width -= 2*margin;
  1180.       //height -= 2*margin;
  1181.  
  1182.       // reset state
  1183.       state = START;
  1184.       FunScroll.dbg("State: START");
  1185.    }
  1186.    
  1187.    void animateImage()
  1188.    {
  1189.       if (!imageLoaded) {
  1190.      if (checkImage()) 
  1191.         resetImage();
  1192.      else {
  1193.         state = DONE;
  1194.         return;
  1195.      }
  1196.       }
  1197.  
  1198.       boolean switchState = false;
  1199.       int scroll = FunScrollAttr.NONE;
  1200.       switch (state) {
  1201.       case START:
  1202.      // start sequence
  1203.      scroll = attr.startScroll();
  1204.      if (scroll == FunScrollAttr.NONE) {
  1205.         // no animation;     just switch state
  1206.         switchState = true;
  1207.      }
  1208.      else if (scroll == FunScrollAttr.FADE) {
  1209.         if (startFadeTrans != null && startFadeTrans.done()) {
  1210.            // fade frames all displayed; switch state
  1211.            switchState = true;
  1212.         }
  1213.      }
  1214.      else {
  1215.         // some kind of animation; check if image displ.
  1216.         if (imageDisplayed(scroll)) {
  1217.            // yupp; switch state
  1218.            switchState = true;
  1219.         }
  1220.      }
  1221.  
  1222.      if (!switchState) {
  1223.         // just move image (scroll)
  1224.         moveImage(scroll);
  1225.         break;
  1226.      }
  1227.      
  1228.      // switch state
  1229.      state = SHOW;
  1230.      FunScroll.dbg("State: SHOW");
  1231.      delayCount = attr.showDelay();
  1232.      // fall trough!
  1233.      
  1234.       case SHOW:
  1235.      // show sequence
  1236.      if (--delayCount >= 0) {
  1237.         // delay. I.e break out
  1238.         break;
  1239.      }
  1240.      
  1241.      // switch state
  1242.      state = END;
  1243.      FunScroll.dbg("State: END");
  1244.      // fall trough!
  1245.      
  1246.       case END:
  1247.      // end sequence
  1248.      if (attr.endScroll() == attr.FADE) {
  1249.         if (endFadeTrans != null && endFadeTrans.done()) {
  1250.            // fade frames all displayed
  1251.            state = DONE;
  1252.            FunScroll.dbg("State: DONE");
  1253.            delayCount = attr.endDelay();
  1254.            // fall trough!
  1255.         }
  1256.         else
  1257.            break;
  1258.      }
  1259.      
  1260.      // check if image still visible
  1261.      else if (!imageVisible(attr.endScroll()) ||
  1262.          attr.endScroll() == FunScrollAttr.NONE) {
  1263.         // none visible or no end animation; switch state
  1264.         state = DONE;
  1265.         FunScroll.dbg("State: DONE");
  1266.         delayCount = attr.endDelay();
  1267.         return;
  1268.      }
  1269.      else {
  1270.         moveImage(attr.endScroll());
  1271.      }
  1272.      break;
  1273.      
  1274.       case DONE:
  1275.      // done sequence; just delay
  1276.      delayCount--;
  1277.      break;
  1278.       }
  1279.    }
  1280.    
  1281.    void moveImage(int scroll)
  1282.    {
  1283.       switch (scroll) {
  1284.       case FunScrollAttr.LEFT:
  1285.      xStart -= dx;
  1286.      break;
  1287.       case FunScrollAttr.RIGHT:
  1288.      xStart += dx;
  1289.      break;
  1290.       case FunScrollAttr.UP:
  1291.      yStart -= dy;
  1292.      break;
  1293.       case FunScrollAttr.DOWN:
  1294.      yStart += dy;
  1295.      break;
  1296.       }
  1297.    }
  1298.  
  1299.    boolean imageDisplayed(int scroll)
  1300.    {
  1301.       switch (scroll) {
  1302.       case FunScrollAttr.LEFT:
  1303.      // scrolling left
  1304.      if (imageWidth > width) {
  1305.         // image is wider that applet width
  1306.         if (imageWidth+xStart < width-4*dx)
  1307.            return true;
  1308.      }
  1309.      else {
  1310.         int appletMidPoint = width/2;
  1311.         int imageMidPoint = xStart+imageWidth/2;
  1312.         if (imageMidPoint <= appletMidPoint)
  1313.            return true;
  1314.      }
  1315.      break;
  1316.      
  1317.       case FunScrollAttr.RIGHT:
  1318.      // scrolling right
  1319.      if (imageWidth > width) {
  1320.         // image is wider that applet width
  1321.         if (xPos[0]+xStart > 4*dx)
  1322.            return true;
  1323.      }
  1324.      else {
  1325.         int appletMidPoint = width/2;
  1326.         int imageMidPoint = xStart+imageWidth/2;
  1327.         if (imageMidPoint >= appletMidPoint)
  1328.            return true;
  1329.      }
  1330.      break;
  1331.      
  1332.       case FunScrollAttr.UP:
  1333.      // scrolling up
  1334.         if (yStart <= (height-imageHeight)/2-descent)
  1335.            return true;
  1336.         break;
  1337.         
  1338.       case FunScrollAttr.DOWN:
  1339.      // scrolling down
  1340.      if (yStart >= (height-imageHeight)/2-descent) 
  1341.         return true;
  1342.      break;
  1343.       }
  1344.       return false;
  1345.    }
  1346.    
  1347.    boolean imageVisible(int scroll)
  1348.    {
  1349.       boolean visible = (xStart+imageWidth > margin &&
  1350.              xStart < width-margin &&
  1351.              yStart+imageHeight > margin &&
  1352.              yStart < height-margin);
  1353.       return visible;
  1354.    }
  1355.    
  1356.  
  1357.    void paintImage(Graphics g)
  1358.    {
  1359.       if (imageWidth == 0)
  1360.      return;
  1361.  
  1362.       int x = xStart;
  1363.       int y = yStart;
  1364.  
  1365.       if ((state == END || state == DONE) &&
  1366.       attr.endScroll() == FunScrollAttr.FADE) {
  1367.      if (endFadeTrans == null) {
  1368.         endFadeTrans = new FunScrollFade(
  1369.            appl, width + margin, height + margin);
  1370.         endFadeTrans.initToImage(appl.getOffImage());
  1371.      }
  1372.      else {
  1373.         endFadeTrans.drawFrame(g);
  1374.         return;
  1375.      }
  1376.       }
  1377.  
  1378.       if (state == START && attr.startScroll() == FunScrollAttr.FADE) {
  1379.      if (startFadeTrans == null) {
  1380.         startFadeTrans = new FunScrollFade(
  1381.            appl, width + margin, height + margin);
  1382.         startFadeTrans.initFromImage(appl.getOffImage());
  1383.         // create image to transform to...
  1384.         Image offImage = imageCreateEmpty(width + margin, height + margin);
  1385.         Graphics gr = offImage.getGraphics();
  1386.         Image fromImage = startFadeTrans.getFromImage();
  1387.         gr.drawImage(fromImage, 0, 0, null);
  1388.         gr.drawImage(image, x, y, null);
  1389.         startFadeTrans.initToImage(offImage);
  1390.         gr.dispose();
  1391.         gr = null;
  1392.      }
  1393.      startFadeTrans.drawFrame(g);
  1394.      return;
  1395.       }
  1396.       
  1397.       switch (attr.style()) {
  1398.       case FunScrollAttr.SINEWAVE:
  1399.      sinDegree -= 15;
  1400.      if (sinDegree <= 0)
  1401.         sinDegree = 360;
  1402.      y += (int) (Math.sin(sinDegree*3.1414/180) * imageHeight/4);
  1403.      break;
  1404.       case FunScrollAttr.NERVOUS:
  1405.      x = (int)(Math.random() * 2) + xStart;
  1406.      y = (int)(Math.random() * 4) + yStart;
  1407.      break;
  1408.       default:
  1409.      break;
  1410.       }
  1411.       g.drawImage(image, x, y, null);
  1412.  
  1413.       if (state == END && attr.endScroll() == FunScrollAttr.FADE) {
  1414.      if (endFadeTrans != null) 
  1415.         endFadeTrans.initFromImage(appl.getOffImage());
  1416.       }
  1417.    }
  1418.  
  1419.    // -----------------------------------------------------------------
  1420.    // Various Image support methods
  1421.  
  1422.    
  1423.    /**
  1424.     * Start loading image with spec. id 
  1425.     */
  1426.    void imageLoad(Image image, int id)
  1427.    {
  1428.       if (mediaTracker == null)
  1429.      mediaTracker = new MediaTracker(appl);
  1430.       mediaTracker.addImage(image, id);
  1431.    }
  1432.    
  1433.    /**
  1434.     * Check if image with spec. id is loaded
  1435.     */
  1436.    boolean imageCheck(int id)
  1437.    {
  1438.       if (!mediaTracker.checkID(id, true)) {
  1439.      FunScroll.dbg("imageCheck: loading...");
  1440.      return false;        // still loading
  1441.       }
  1442.       
  1443.       if (mediaTracker.isErrorID(id)) {
  1444.      // error during load
  1445.      // BUG ALERT: we should display an error message!
  1446.      FunScroll.dbg("imageCheck: error...");
  1447.      return false;
  1448.       }
  1449.  
  1450.       return true;
  1451.    }
  1452.  
  1453.    /**
  1454.     * Create empty image
  1455.     */
  1456.    Image imageCreateEmpty(int width, int height)
  1457.    {
  1458.       return appl.createImage(width, height);
  1459.    }
  1460.  
  1461.    /**
  1462.     * Draw string to image
  1463.     */
  1464.    void imageDrawString(Image image, String text, int x, int y)
  1465.    {
  1466.       Graphics g = bgImage.getGraphics();
  1467.       g.setColor(bg);
  1468.       imageWidth = image.getWidth(null);
  1469.       imageHeight = image.getHeight(null);
  1470.       g.fillRect(0, 0, imageWidth, imageHeight);
  1471.       g.setColor(fg);
  1472.       g.setFont(font);
  1473.       // BUG ALERT: should use the below to handle several lines
  1474.       g.drawString(text, x, y+ascent);
  1475.       g.dispose();
  1476.    }
  1477.  
  1478.    // -----------------------------------------------------------------
  1479.    // Various methods to handle text string separated into lines
  1480.    
  1481.    /**
  1482.     * Get lines, by splitting a a string into (/n separated) lines
  1483.     */
  1484.    String[] getLines(String text)
  1485.    {
  1486.       Vector linesOftext = new Vector();
  1487.       StringTokenizer st = new StringTokenizer(text, "\\n\n", true);
  1488.       boolean escape = false;
  1489.       boolean newLine = false;
  1490.       String line = new String();
  1491.       String token = null;
  1492.       while (st.hasMoreTokens()) {
  1493.      token = st.nextToken();
  1494.      if (token.equals("\\")) 
  1495.         // escaped characted; wait for next character
  1496.         escape = true;
  1497.      else if (token.equals("n") && escape) {
  1498.         // got "\n" - line break
  1499.         newLine = true;
  1500.         escape = false;
  1501.      }
  1502.      else if (token.equals("\n")) 
  1503.         newLine = true;
  1504.      else {
  1505.         if (escape)
  1506.            line += "\\";
  1507.         line += token;
  1508.      }
  1509.      if (newLine) {
  1510.         linesOftext.addElement(line);
  1511.         line = new String();
  1512.         token = null;
  1513.         newLine = false;
  1514.      }
  1515.       }
  1516.       if (line.length() > 0) {
  1517.      linesOftext.addElement(line);
  1518.       }
  1519.       int noOfLines = linesOftext.size();
  1520.       String[] lines = new String[noOfLines];
  1521.       for (int i=0; i < noOfLines; i++) 
  1522.          lines[i] = (String)linesOftext.elementAt(i);
  1523.       return lines;
  1524.    }
  1525.  
  1526.    /**
  1527.     * Get line widths (array of int) for each line
  1528.     */
  1529.    int[] getLineWidths(String[] lines)
  1530.    {
  1531.       int noOfLines = lines.length;
  1532.       int[] lineWidths = new int[noOfLines];
  1533.       for (int i=0; i < noOfLines; i++) {
  1534.      lineWidths[i] = fontMetrics.stringWidth(lines[i]);
  1535.       }
  1536.       return lineWidths;
  1537.    }
  1538. }
  1539.  
  1540.