/**
   @version 1.30 2000-06-03
   @author Cay Horstmann
*/

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class CustomEventTest
{  
   public static void main(String[] args)
   {  
      CustomEventFrame frame = new CustomEventFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.show();
   }
}

/**
   A frame with a panel that displays falling raindrops
*/
class CustomEventFrame extends JFrame
{
   public CustomEventFrame()
   {
      setTitle("CustomEventTest");
      setSize(WIDTH, HEIGHT);

      // add frame to panel

      CustomEventPanel panel = new CustomEventPanel();
      Container contentPane = getContentPane();
      contentPane.add(panel);
   }

   public static final int WIDTH = 300;
   public static final int HEIGHT = 200;  
}

/**
   A panel that displays falling rain drops
*/
class CustomEventPanel extends JPanel
{ 
   public CustomEventPanel()
   {  
      y = 0;
      circles = new ArrayList();

      Timer t = new Timer(100);
      TimerAction listener = new TimerAction();
      t.addTimerListener(listener);
   }

   public void paintComponent(Graphics g)
   {  
      super.paintComponent(g);
      Graphics2D g2 = (Graphics2D)g;

      // translate the origin to create illusion of falling drops
      g2.translate(0, y);

      // draw all circles
      for (int i = 0; i < circles.size(); i++)
         g2.draw((Ellipse2D)circles.get(i));
   }

   private ArrayList circles;
   private int y;

   private class TimerAction implements TimerListener
   {  
      public void timeElapsed(TimerEvent event)
      {  
         if (getWidth() == 0) return; // panel not yet shown

         // add another circle
         int x = generator.nextInt(getWidth());
         Ellipse2D circle = new Ellipse2D.Double(x, -y, 
            SIZE, SIZE);
         circles.add(circle);

         // shift up the origin
         y++;

         repaint();
      }

      private Random generator = new Random();
      private static final int SIZE = 9;
   }
}

/**
   A custom event class.
*/
class TimerEvent extends AWTEvent
{  
   public TimerEvent(Timer t) { super(t, TIMER_EVENT); }
   public static final int TIMER_EVENT
      = AWTEvent.RESERVED_ID_MAX  + 5555;
}

/**
   A custom event listener interface.
*/
interface TimerListener extends EventListener
{  
   public void timeElapsed(TimerEvent event);
}

/**
   A custom timer class that is the source of timer events.
*/
class Timer extends JComponent implements Runnable
{  
   public Timer(int i)
   {  
      listenerList = new EventListenerList();
      interval = i;
      Thread t = new Thread(this);
      t.start();
   }

   /**
      Adds a timer listener
      @param listener the listener to add
   */
   public void addTimerListener(TimerListener listener)
   {  
      listenerList.add(TimerListener.class, listener);
   }

   /**
      Removes a timer listener
      @param listener the listener to remove
   */
   public void removeTimerListener(TimerListener listener)
   {  
      listenerList.remove(TimerListener.class, listener);
   }


   /**
      Posts a new timer event every <code>interval</code>
      milliseconds.
   */
   public void run()
   {  
      while (true)
      {  
         try { Thread.sleep(interval); }
         catch(InterruptedException e) {}
      
         TimerEvent event = new TimerEvent(this);
         
         EventQueue queue 
            = Toolkit.getDefaultToolkit().getSystemEventQueue();
         queue.postEvent(event);
      }
   }

   public void processEvent(AWTEvent event)
   {  
      if (event instanceof TimerEvent)
      {
         EventListener[] listeners = listenerList.getListeners(
            TimerListener.class);
         for (int i = 0; i < listeners.length; i++)
            ((TimerListener)listeners[i]).timeElapsed(
               (TimerEvent)event);
      }
      else
         super.processEvent(event);
   }

   private int interval;
   private EventListenerList listeners;
}

