// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.<P>
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.<P>
// 
// To obtain a copy of the GNU General Public License write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. <P>


package rob.applets ;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

/** 
 * An applet that displays a simple animation using double-buffering 
 * and clipping.  To add the applet to a web page put the class file in the 
 * same directory as the page and use the tag: 
 * <PRE><code>
 *
 * <APPLET code="rob.applets.BouncingTextApplet" width=500 height=300>
 * <PARAM name="text" value="Text to display">
 * </APPLET>
 *
 * </code></PRE>
 **/
public class BouncingTextApplet extends Applet implements Runnable, ActionListener {

    private int x = 150, y = 100 ;    // Initial position of text
    private int dx = 5, dy = 3;       // Trajectory of text
    private int adx = dx, ady = dy;   // Absolute values of dx and dy
    private Dimension size;           // The size of the applet
    private Image buffer;             // The off-screen image for double-buffering
    private Graphics bufferGraphics;  // A Graphics object for the buffer
    private Thread runner;            // Thread that performs the animation
    private boolean stop;             // A flag asking animation thread to stop
    private String text ;             // The text to display

    private int ascent, height, width ; // Font details

    /** Set up an off-screen Image for double-buffering and some buttons */
    public void init() {

	// Set the font and the background
	setFont(new Font("SansSerif", 14, Font.PLAIN)) ;
	setBackground(Color.white) ;

	// A box to hold the buttons so that they don't look horrible
	Panel box = new Panel(new FlowLayout(FlowLayout.CENTER, 15, 5)) ;
	Button b = new Button("Start") ;
	b.addActionListener(this) ;
	box.add(b) ;
	b = new Button("Stop") ;
	b.addActionListener(this) ;
	box.add(b) ;

	// Set the layout so that we can put the button box across the bottom
	setLayout(new BorderLayout()) ;
	add(box, BorderLayout.SOUTH) ;

	// We need this information to implement double buffering so that
	// we can move the text without flicker
	size = this.size();
	buffer = this.createImage(size.width, size.height);
	bufferGraphics = buffer.getGraphics();

	// Read the "text" parameter - it should look like:
	// <PARAM name="text" value="the text to display">
	text = getParameter("text") ;
	if (text == null || text.equals("")) text = "Hello World" ;

	// Get the size of the text so that we know how to draw it.
	FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont()) ;
	ascent = fm.getAscent() ;
	height = fm.getHeight() ;
	width = fm.stringWidth(text) ;

    }

    /**
     * Listen for button clicks and act on them.  The command will be either
     * "Start" or "Stop".  The methods of those names are called in response.
     *
     * @param e The <code>ActionEvent</code> generated by the button click - it may
     *          be used to find the "Action Command", in this case the text on the
     *          button.
     */
    public void actionPerformed(ActionEvent e) {
	if ("Start".equals(e.getActionCommand())) start() ;
	else stop() ;
    }

    /** Draw the text at its current position, using double-buffering */
    public void paint(Graphics g) {
	// Draw into the off-screen buffer.

	// Set the clip rectangle of bufferGraphics to be the same as that of g.
	bufferGraphics.setClip(g.getClip());
	bufferGraphics.setColor(this.getBackground());  // set background colour
	bufferGraphics.fillRect(0, 0, size.width, size.height); // clear the buffer
	bufferGraphics.setColor(Color.red);             // set foreground colour
	bufferGraphics.drawString(text, x, y) ;         // draw the text

	// Then copy the off-screen buffer onto the screen
	g.drawImage(buffer, 0, 0, this);
    }

    /** Don't clear the screen; just call paint() immediately
     *   It is important to override this method like this for double-buffering */
    public void update(Graphics g) { paint(g); }

    /** 
     * The body of the animation thread; the text needs to be moved in a separate
     * thread of execution otherwise the browser will not be able to control the
     * applet.  This is done with the following steps:
     * <OL>
     * <LI> Indicating that we implement the <code>Runnable</code> interface - 
     *      that requires that we contain a method with signature
     *      <code>public void run()</code>
     * <LI> Implement a method <code>public void run()</code> containing the code
     *      we want executed.  There should be some way to signal the thread to
     *      stop so that when the browser tells the applet to stop (by calling its
     *      <code>stop</code> method) we can stop the text. <P>
     *      <B>Note</B> One of the bugs in Netscape's implementation of the JVM
     *      is that calling <code>interrupt</code> on a thread, catching the
     *      <code>InterruptedException</code> and then calling 
     *      <code>interrupt</code> again simply does not work.  Thus you must use
     *      your own flag for signalling.
     * <LI> When the applet is started (by the browser calling our 
     *      <code>start</code> method) create a new thread with
     *      <code>Thread runner = new Thread(this)</code> and start it with
     *      <code>runner.start()</code>.
     * </OL><P>
     *
     * This method shifts the text a little, calls <code>repaint</code> with
     * the region that has changed, waits for 1/20th of a second and repeats.
     */
    public void run() {
	while(!stop) {
	    // Bounce the text if we've hit an edge.
	    if ((x + dx < 2) || (x + width + dx > size.width)) dx = -dx;
	    if ((y + dy < height) || (y + 50 + dy > size.height)) dy = -dy;

	    // Move the text.
	    x += dx;  y += dy;
	    
	    // Ask the browser to call our paint() method to redraw the circle
	    // at its new position.  Tell repaint what portion of the applet needs
	    // be redrawn.

	    // repaint only what has changed
	    repaint(x-adx-2, y-ady-ascent, width+4+2*adx, height+2*ady);

	    // Now pause 1/20th of a second before drawing the text again.
	    try { Thread.sleep(50); } catch (InterruptedException e) { ; }
	}
	runner = null;
    }

    /** Start the animation thread */
    public void start() {
	if (runner == null) {
	    stop = false;
	    runner = new Thread(this);
	    runner.start();
	}
    }

    /** Stop the animation thread */
    public void stop() { stop = true; }

}
