import java.applet.*; // inherit from Applet import java.awt.*; // Window I/O classes import java.awt.event.*; // Event classes // Dilip Barman November 22, 1997 // file DilipsAnimationApplet.java // 3rd java homework assignment to play with multimedia and double buffering. // We can implement any applet or application to illustrate these concepts. // // * * * B A C K G R O U N D O N D O U B L E B U F F E R I N G * * * // // What is double buffering? It is a way to smoothly display a series of // images. If we keep updating the viewing area directly, we get flicker as // we update the individual graphic elements. Instead, we prepare a new // image in an off-screen buffer and only when it's done and we're ready to // refresh the screen, then we copy or "blit" (bit block transfer) the buffer // onto a screen area. // // To do this, we declare one or more off screen images of type Image, as well // as offscreen graphic contexts (of type Graphics) into which we draw - e.g.: // Image offscreenImage; Graphics offscreenImageG; // To copy the image, we can use a method in java.awt.Graphics: // public abstract boolean drawImage (Image img, int x, int y, // Color bgcolor, ImageObserver observer) // which fills a rectangle of color bgcolor into the observer area, and then // copies, starting from the upper left (x,y) of img, the img image. // // When we're ready to update the screen, in the case of an applet, we call // the applet's repaint method. repaint() implicitly calls update(), which // clears the background and then finally calls paint(). Typically, then, // paint() is overriden with a method like drawImage() to do the copying // from the buffer to the screen: // public void paint ( Graphics g ) // { g.drawImage ( offscreenImage, 0, 0, Color.white, this ); } // One last note on rendering the image; update()'s clearing of the background // causes flicker, so we override it to just call paint(). // // * * * B A C K G R O U N D O N M U L T I - T H R E A D I N G * * * // // This is our first application where we will be using multi-threading. // To accomplish animation, we will have objects on the drawing surface which // move, so they can be modeled as having separate threads of control. There // are two approaches to implementing threads in java. The first approach is // for the class to implement the Runnable interface. The class must define // a run() method, which the java virtual machine calls when the thread is // given control. An easier approach, that we'll use here, is for the class // to extend the java Thread class (which itself implements Runnable). So, // we can have a class DilipsThread which extends Thread and defines the // run() method. We define threads of type AnimationObject and run() is used // in the AnimationObject definition. // 11/29/97: I've been frustrated working on this for a week and not being // able to resolve a bug having to do with using the AnimationObject class, // so finally started from scratch and have bouncing balls pretty much working. // 11/30/97: Finished it up by cleaning up code and by adding squares. Now new // objects can be added as circles or squares, each with 50% probability. public class DilipsAnimationApplet extends Applet implements ActionListener // applet, listen for events like mouse clicks and window close { // *********** Debug Variables *********** static DebugWindow debugWindow = new DebugWindow(); private boolean debugMode = true; // set to true to enable debug window msgs // can use statements like this in code: // if (debugMode) { debugWindow.print("Just created frame; x is " + x); } // *********** Constants *********** // *********** Class Constants *********** static final String frameTitle = "Dilip's Bouncing Circles and Squares"; static final int frameTopLx = 10; // Bounds for frame static final int frameTopLy = 190; // 200 static final int frameWidth = 475; // 450 static final int frameHeight = 400; // 380 // *********** Class Variables *********** // *********** Instance Variables *********** int maxNumObjects = 10; // set to however many objects you want MyFrame frame = new MyFrame (); // closeable frame def. in MyFrame.java Panel actionPanel = new Panel(); // panel to do the animation Panel buttonPanel = new Panel(); // panel to display buttons Panel leftBorder = new Panel(); // dummy - just to create L and R borders Panel rightBorder = new Panel(); // " TextField message = new TextField(" ", 40); // 40-char message about # objects on pane Button addButton = new Button ( "Add" ); // Button to add new object to animation Button deleteButton = new Button ( "Delete" ); Button resetButton = new Button ( "Reset" ); Button startButton = new Button ( "Start" ); Button stopButton = new Button ( "Stop" ); Button quitButton = new Button ( "Quit" ); AnimationObject animationObjects[]; // array to hold each AnimationObject int currentObject = - 1; Image offscreenImage; Graphics offscreenImageG; Graphics actionPanelG; Dimension d; // *********** Standard Applet Methods *********** // Now we define methods. Applets look for 4 methods - init, start, stop, and destroy // init() is called when the applet is loaded, start() when it starts execution (i.e., // right after init() and whenever we revisit this web page), stop() when we leave this // web page, and destroy() when we leave the browser. Since this is running in its own // frame, all we need is init() - which creates the frame. public void init() { createFrame (); // Build the user interface d = actionPanel.getSize (); actionPanelG = actionPanel.getGraphics (); offscreenImage = this.createImage ( d.width, d.height ); offscreenImageG = offscreenImage.getGraphics (); offscreenImageG.setColor ( Color.white ); offscreenImageG.fillRect ( 0, 0, d.width, d.height ); // this.addMouseListener ( this ); if (debugMode) { debugWindow.print("About to define " + maxNumObjects + " AnimationObjects"); } animationObjects = new AnimationObject [maxNumObjects]; if (debugMode) { debugWindow.print("Done defining the " + maxNumObjects + " AnimationObjects; about to just loopAndRepaint()"); } loopAndRepaint(); } // end init // *********** Methods to Update the Screen *********** private void loopAndRepaint ( ) { while ( true ) { try { Thread.sleep ( 20 ); } catch ( InterruptedException e) {;} repaint (); } // end while } public void update ( Graphics g ) { paint ( g ); } public void paint ( Graphics g ) { actionPanelG.drawImage ( offscreenImage, 0, 0, this ); } // *********** Other Methods *********** // Method to create and display the frame private void createFrame ( ) { setDefaultBoundsColors (); addComponentsToFrame (); listenForEvents (); frame.show (); // Finally, show the frame! } // end createFrame() private class method // Method to setup initial sizes, colors, etc. of components private void setDefaultBoundsColors ( ) { frame.setBounds(frameTopLx, frameTopLy, frameWidth, frameHeight); frame.setTitle (frameTitle); frame.setBackground (Color.gray); buttonPanel.setBackground (Color.darkGray); leftBorder.setBackground (Color.darkGray); rightBorder.setBackground (Color.darkGray); actionPanel.setBackground (Color.white); message.setBackground (Color.darkGray); message.setForeground (Color.lightGray); message.setText ("Click 'Add' to begin adding objects"); message.setEditable (false); } // end setDefaultBoundsColors() // Method to add the components using simple BorderLayout layout manager private void addComponentsToFrame () { frame.setLayout (new BorderLayout()); buttonPanel.add (addButton); buttonPanel.add (deleteButton); buttonPanel.add (resetButton); buttonPanel.add (startButton); buttonPanel.add (stopButton); buttonPanel.add (quitButton); buttonPanel.add (message); frame.add ("North", message); frame.add ("Center", actionPanel); frame.add ("South", buttonPanel); frame.add ("West", leftBorder); frame.add ("East", rightBorder); } // end addComponentsToFrame() // Method to register to listen for events we need to react to private void listenForEvents () { // Listen on events like close (handle in MyFrame class) frame.addWindowListener (frame); // Listen for button pushes addButton.addActionListener ( this ); deleteButton.addActionListener ( this ); resetButton.addActionListener ( this ); startButton.addActionListener ( this ); stopButton.addActionListener ( this ); quitButton.addActionListener ( this ); } // end listenForEvents() //*********** Interface Methods *********** // Now define IMPLEMENTORS - handle button presses public void actionPerformed ( ActionEvent e ) { Object s = e.getSource(); // --------------- ADD BUTTON --------------- if ( s == addButton ) { if ( currentObject < (maxNumObjects - 1) ) { ++currentObject; if (debugMode) { debugWindow.print("Added new object #" + currentObject); } animationObjects [currentObject] = new AnimationObject ( offscreenImageG, d, this ); animationObjects[currentObject].start(); if (currentObject == 0) { message.setText ( "First object added; room for " + (maxNumObjects-currentObject-1) + " more" );} else if (currentObject == (maxNumObjects-1) ) { message.setText ( maxNumObjects + " (maximum) objects; room for no more" ); } else { message.setText ( (currentObject+1)+" objects; room for " + (maxNumObjects-currentObject-1) + " more" ); } } // end if currentObject < (maxNumObjects-1) else { message.setText ( "Maximum number of animationObjects exceeded." ); } } // end addButton // --------------- DELETE BUTTON --------------- if ( s == deleteButton ) { synchronized ( offscreenImageG ) { animationObjects[currentObject].delete(); animationObjects[currentObject].stop(); } --currentObject; message.setText ( "AnimationObject deleted; room for " + (maxNumObjects-currentObject-1)+" more" ); if (debugMode) { debugWindow.print("Removed object #" + (currentObject+1) ); } } // end deleteButton // --------------- RESET BUTTON --------------- if ( s == resetButton ) { for ( int i = currentObject; i >= 0; i-- ) { synchronized ( offscreenImageG ) { animationObjects[i].delete(); animationObjects[i].stop(); } if (debugMode) { debugWindow.print("Resetting - removed object #" + i ); } } // end for loop currentObject = -1; message.setText ( "Reset - Click 'Add' to begin adding objects again." ); if (debugMode) { debugWindow.print("Reset complete"); } } // end resetButton // --------------- START BUTTON --------------- if ( s == startButton ) { for ( int i = 0; i <= currentObject; i++ ) { animationObjects[i].resume(); } message.setText ( "Action resumed." ); if (debugMode) { debugWindow.print("Action resumed"); } } // --------------- STOP BUTTON --------------- if ( s == stopButton ) { for ( int i = 0; i <= currentObject; i++ ) { animationObjects[i].suspend(); } message.setText ( "Action suspended." ); if (debugMode) { debugWindow.print("Action suspended"); } } // --------------- QUIT BUTTON --------------- if ( s == quitButton ) { for ( int i = 0; i <= currentObject; i++ ) { animationObjects[i].stop(); } message.setText ( "Applet quitting." ); if (debugMode) { debugWindow.print("About to quit"); debugWindow.setVisible (false); } frame.setVisible (false); // frame.hide(); frame.dispose (); animationObjects[0].quit(); // call AnimationObject's quit() System.exit (0); } // end quitButton } // end actionPerformed } // end DilipsAnimationApplet