import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;

/** DemoPaintPanel3 - similar to <code>DemoPaintPanel2</code> except
* reorganizing the painting objects<p>
*
* This application is identical in behaviour to <code>DemoPaintPanel2</code>.
* However, the design is significantly different.<p>
*
* We introduce the
* tem <i>entities</i> to describe the objects to be painted.  Each graphics
* entity is an instance
* of a custom inner class
* named <code>Entity</code>.<p>
*
* At present, just four types of
* entities are supported: circles, squares, text strings, and images.  These
* are identified by static integers:<p>
*
* <pre>
*     final static int SQUARE = 1;
*     final static int CIRCLE = 2;
*     final static int STRING = 3;
*     final static int IMAGE = 4;
* </pre>
*
* Lots
* of details are hard-coded, such as the painting color and the size of
* the shapes,
* but this is purely to keep the demo simple.  The approach illustrated
* is easily scalable to a richer set of graphics entities.<p>
*
* The <code>Entity</code> class simply
* stores information about a graphics object.  It contains four
* instance variables:<p>
*
* <pre>
*     private Object ent;  // entity to paint (may be null)
*     private int type;    // type of entity
*     private int x;       // x coordinate for painting
*     private int y;       // y coordinate for painting
* </pre>
*
* The only methods in the <code>Entity</code> class are
* the constructor to assign arguments to
* instance variables and four accessor methods to retrieve the instance
* variables.<p>
*
* The <code>ent</code> variable is either <code>null</code> (for circles
* and squares), a <code>String</code> object, (for text strings), or
* an <code>Image</code> object (for images).<p>
*
* The <code>PaintPanel</code> class is rewritten to support the entities just
* described.  The first difference you'll notice is the presence of a new
* instance variable:<p>
*
* <pre>
*     private Vector v;
* </pre>
*
* The vector <code>v</code>
* is simply a place to store all the graphics entities
* to paint.  It can grow or shrink on an as-needed basis.<p>
*
* The four <code><i>drawXxxx</i></code> methods from earlier
* (see <code>DemoPaintPanel2</code>) are replaced by a single method:<p>
*
* <pre>
*     public void drawEntity(Object e, int type, int x, int y)
*     {
*        v.addElement(new Entity(e, type, x, y));
*        this.repaint();
*     }
* </pre>
*
* So the process of drawing a graphics entity involves first adding
* it to a vector as an instance of <code>Entity</code>, then
* invoking <code>repaint</code> on the paint panel.  This is not the
* best approach, since it means all the entities are repainted as
* each entity is added.  Can you think of a way to improve the
* preformance of <code>drawEntity</code>?<p>
*
* The
* <code>repaint</code> method invokes the <code>paintComponent</code>,
* as noted before.  The <code>paintComponent</code> method has
* just three lines:<p>
*
* <pre>
*     public void paintComponent(Graphics g)
*     {
*        super.paintComponent(g); // paint background
*        paintInstructions(g);    
*        paintEntities(g);      
*     }
* </pre>
*
* The <code>paintEntities</code> method simply paints all the graphics
* enitities stored in the vector.  Doing so involves
* retrieving the entities from the vector one by one,
* examining the <code>type</code> variable to determine what type of enitity
* it is, then invoking the appropriate graphics method to paint it.
* Consult the source code for further details.<p>
*
* This new approach neccessitates a slight redesign of the panel's
* <code>clear</code> method:<p>
*
* <pre>
*     public void clear()
*     {
*        v.clear();
*        this.repaint();
*     } 
* </pre>
*
* So, clearing the paint panel involves removing all the entities
* from the vector, then invoking <code>repaint</code> on the paint panel.
* <p>
*
* Oops!  The earlier claim that this application is "identical
* in behaviour" to <code>DemoPaintPanel2</code> is not quite true.
* An important consequence
* of our redesign is that the application window may be resized without
* loosing the contents of the paint panel.<p>
*
* @see <a href="DemoPaintPanel3.java">source code</a>
* @author Scott MacKenzie, 2002
*/
public class DemoPaintPanel3
{
   public static void main(String[] args)
   {
      // use look and feel for my system (Win32)
      try {
         UIManager.setLookAndFeel(
            UIManager.getSystemLookAndFeelClassName());
      } catch (Exception e) {}

      DemoPaintPanel3Frame frame = new DemoPaintPanel3Frame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setTitle("DemoPaintPanel3");
      frame.pack();
      frame.show();
   }
}

class DemoPaintPanel3Frame extends JFrame
implements ActionListener, MouseListener
{
   private PaintPanel pp;
   private JRadioButton square;
   private JRadioButton circle;
   private JRadioButton text;
   private JRadioButton image;
   private JButton clear;
   private JButton exit;

   final Image YORK_LOGO = (new ImageIcon("logo.gif")).getImage();
   final String MESSAGE = "Hello Java World";

   public DemoPaintPanel3Frame()
   {
      // ----------------------------------
      // construct and configure components
      // ----------------------------------

      pp = new PaintPanel();
      pp.setBorder(BorderFactory.createLineBorder(Color.gray));

      square = new JRadioButton("Square");
      circle = new JRadioButton("Circle");
      text = new JRadioButton("Text message");
      image = new JRadioButton("Image");

      ButtonGroup bg = new ButtonGroup();
      bg.add(square);
      bg.add(circle);
      bg.add(text);
      bg.add(image);

      square.setSelected(true);

      clear = new JButton("Clear");
      exit = new JButton("Exit");

      // make buttons the same size

      exit.setMaximumSize(clear.getPreferredSize());

      // -------------
      // add listeners
      // -------------

      pp.addMouseListener(this);
      clear.addActionListener(this);
      exit.addActionListener(this);

      // ------------------
      // arrange components
      // ------------------

      JPanel p1 = new JPanel();
      p1.setLayout(new BoxLayout(p1, BoxLayout.Y_AXIS));
      p1.add(square);
      p1.add(circle);
      p1.add(text);
      p1.add(image);
      p1.add(Box.createRigidArea(new Dimension(0, 10)));  
      p1.add(clear);
      p1.add(Box.createRigidArea(new Dimension(0, 10)));  
      p1.add(exit);
      p1.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

      // set both panels for 'top alignment'       
      pp.setAlignmentY(Component.TOP_ALIGNMENT);
      p1.setAlignmentY(Component.TOP_ALIGNMENT);

      JPanel p = new JPanel();
      p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
      p.add(pp);
      p.add(p1);
      p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

      this.setContentPane(p);
   }

   // -------------------------------
   // implement ActionListener method
   // -------------------------------

   public void actionPerformed(ActionEvent ae)
   {
      Object source = ae.getSource();

      if (source == clear)
         pp.clear();

      else if (source == exit)
         System.exit(0);
   }

   // -----------------------------------
   // implement MouseListener methods (5)
   // -----------------------------------

   public void mouseEntered(MouseEvent me) {}
   public void mouseExited(MouseEvent me) {}
   public void mousePressed(MouseEvent me) {}
   public void mouseReleased(MouseEvent me) {}
   public void mouseClicked(MouseEvent me)
   {
      int x = me.getX();
      int y = me.getY();

      if (square.isSelected())
         pp.drawEntity(null, Entity.SQUARE, x, y);

      else if (circle.isSelected())
         pp.drawEntity(null, Entity.CIRCLE, x, y);

      else if (text.isSelected())
         pp.drawEntity(MESSAGE, Entity.STRING, x, y);

      else if (image.isSelected())
         pp.drawEntity(YORK_LOGO, Entity.IMAGE, x, y); 
   }

   /** An inner class for painting shapes and other graphics.
   */
   class PaintPanel extends JPanel
   {
      private Vector v;

      PaintPanel()
      {
         v = new Vector();
         this.setBackground(Color.pink);
         this.setPreferredSize(new Dimension(250, 250));
      }

      public void paintComponent(Graphics g)
      {
         super.paintComponent(g); // paint background
         paintInstructions(g);    
         paintEntities(g);      
      }

      /** Paint all the entities stored in the vector */
      private void paintEntities(Graphics g)
      {
         Graphics2D g2 = (Graphics2D)g;

         for (int i = 0; i < v.size(); ++i)
         {
            Entity e = (Entity)v.elementAt(i);
            int x = e.getX();
            int y = e.getY();

            switch (e.getType())
            {
               case Entity.SQUARE:
                  g2.setColor(new Color(0, 0, 128));   
                  g2.draw(new Rectangle2D.Double(x, y, 25, 25));
                  break;

               case Entity.CIRCLE:
                  g2.setColor(new Color(128, 0, 0));    
                  g2.draw(new Ellipse2D.Double(x, y, 25, 25));
                  break;

               case Entity.STRING:
                  g2.setColor(new Color(0, 128, 0));   
                  g2.drawString((String)e.getEntity(), x, y);
                  break;

               case Entity.IMAGE:
                  g2.drawImage((Image)e.getEntity(), x, y, this);              
            }
         }
      }

      /** Paint "Click to Paint" at the top of the panel */
      private void paintInstructions(Graphics g)
      {
         final String INSTRUCTIONS = "Click to Paint";

         // set font characteristics for Instructions

         Graphics2D g2 = (Graphics2D)g;  
         g2.setColor(Color.gray);
         Font f = g2.getFont(); // save current font
         g2.setFont(new Font("SansSerif", Font.BOLD, 16));
        
         // get width/height of Instructions string (in pixels)

         FontMetrics fm = g2.getFontMetrics();
         int width = fm.stringWidth(INSTRUCTIONS);
         int height = fm.getHeight();

         // get width of panel (in pixels)

         int w = this.getWidth();           

         // draw it!

         g2.drawString(INSTRUCTIONS, w/2 - width/2, height);
         g2.setFont(f); // restore font
      }

      /** Clear the panel (by clearing the vector of entities) */
      public void clear()
      {
         v.clear();
         this.repaint();
      }

      /** Draw an entity */
      public void drawEntity(Object e, int type, int x, int y)
      {
         v.addElement(new Entity(e, type, x, y));
         this.repaint();
      }
   }

   /** A simple class to store information about graphics entities */
   private class Entity
   {
      final static int SQUARE = 1;
      final static int CIRCLE = 2;
      final static int STRING = 3;
      final static int IMAGE = 4;

      private Object ent;  // entity to paint (may be null)
      private int type;    // type of entity
      private int x;       // x coordinate for painting
      private int y;       // y coordinate for painting

      Entity(Object entArg, int typeArg, int xArg, int yArg)
      {
         ent = entArg;
         type = typeArg;
         x = xArg;
         y = yArg;
      }

      public Object getEntity() { return ent; }
      public int getType() { return type; }
      public int getX() { return x; }
      public int getY() { return y; }
   }
}

