
/* 
    The ShapeDraw applet lets the user add small colored shapes to
    a drawing area and then drag them around.  The shapes are rectangles,
    ovals, and roundrects.  The user adds a shape to the canvas by
    clicking on a button.  The shape is added at the upper left corner
    of the canvas.  The color of the shape is given by the current
    setting of a pop-up menu.  The user can drag the shapes with the
    mouse.  Ordinarily, the shapes maintain a given back-to-front order.
    However, if the user shift-clicks on a shape, that shape will be
    brought to the front.
    
    A menu can be popped up on a shape (by right-clicking or performing
    some othe platform-dependent action).  This menu allows the user
    to change the size and color of a shape.  It is also possible to
    delete the shape and to bring it to the front.
    
    This file defines the applet class plus several other classes used
    by the applet, namely:  ShapeCanvas, Shape, RectShape, OvalShape,
    and RoundRectShape.
    
    David Eck
    July 28,  1998
*/


import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
import java.util.Vector;


public class ShapeDrawWithMenu extends Applet {
   
   public void init() {  
        // Set up the applet's GUI.  It consists of a canvas, or drawing area,
        // plus a row of controls below the canvas.  The controls include three
        // buttons which are used to add shapes to the canvas and a Choice menu
        // that is used to select the color used for a shape when it is created.
        // The canvas is set as the "listener" for these controls so that it can
        // respond to the user's actions.  (The pop-up menu is created by the canvas.)
   
      setBackground(Color.lightGray);
      
      ShapeCanvas canvas = new ShapeCanvas();  // create the canvas

      Choice colorChoice = new Choice();  // color choice menu
      colorChoice.add("Red");
      colorChoice.add("Green");
      colorChoice.add("Blue");
      colorChoice.add("Cyan");
      colorChoice.add("Magenta");
      colorChoice.add("Yellow");
      colorChoice.add("Black");
      colorChoice.add("White");
      colorChoice.addItemListener(canvas);
      
      Button rectButton = new Button("Rect");    // buttons for adding shapes
      rectButton.addActionListener(canvas);

      Button ovalButton = new Button("Oval");
      ovalButton.addActionListener(canvas);

      Button roundRectButton = new Button("RoundRect");
      roundRectButton.addActionListener(canvas);
      
      Panel bottom = new Panel();   // a Panel to hold the control buttons
      bottom.setLayout(new GridLayout(1,4,3,3));
      bottom.add(rectButton);
      bottom.add(ovalButton);
      bottom.add(roundRectButton);
      bottom.add(colorChoice);
   
      setLayout(new BorderLayout(3,3));
      add("Center",canvas);              // add canvas and controls to the applet
      add("South",bottom);
      
   }
   
   public Insets getInsets() {
        // Says how much space to leave between the edges of the applet and the
        // components in the applet.
      return new Insets(3,3,3,3);
   }
   
}  // end class ShapeDraw


class ShapeCanvas extends Canvas implements ActionListener, ItemListener,
                                            MouseListener, MouseMotionListener {
    
      // This class represents a canvas that can display colored shapes and
      // let the user drag them around.  It uses an off-screen images to 
      // make the dragging look as smooth as possible.  A pop-up menu is
      // added to the canvas that can be used to performa certain actions
      // on the shapes;

   Image offScreenCanvas = null;   // off-screen image used for double buffering
   Graphics offScreenGraphics;     // graphics context for drawing to offScreenCanvas
   Vector shapes = new Vector();   // holds a list of the shapes that are displayed on the canvas
   Color currentColor = Color.red; // current color; when a shape is created, this is its color
   

   ShapeCanvas() {
        // Constructor: set background color to white, set up listeners to respond to mouse actions,
        //              and set up the pop-up menu

      setBackground(Color.white);

      addMouseListener(this);
      addMouseMotionListener(this);

      popup = new PopupMenu();
      popup.add("Red");
      popup.add("Green");
      popup.add("Blue");
      popup.add("Cyan");
      popup.add("Magenta");
      popup.add("Yellow");
      popup.add("Black");
      popup.add("White");
      popup.addSeparator();
      popup.add("Big");
      popup.add("Medium");
      popup.add("Small");
      popup.addSeparator();
      popup.add("Delete");
      popup.add("Bring To Front");
      add(popup);
      popup.addActionListener(this);

   } // end construtor

   synchronized public void paint(Graphics g) {
        // In the paint method, everything is drawn to an off-screen canvas, and then
        // that canvas is copied onto the screen.
      makeOffScreenCanvas();
      g.drawImage(offScreenCanvas,0,0,this);
   }
   
   public void update(Graphics g) {
        // Update method is called when canvas is to be redrawn.
        // Just call the paint method.
      paint(g);
   }
   
   void makeOffScreenCanvas() {
         // Erase the off-screen canvas and redraw all the shapes in the list.
         // (First, if canvas has not yet been created, then create it.)
      if (offScreenCanvas == null) {
         offScreenCanvas = createImage(getSize().width,getSize().height);
         offScreenGraphics = offScreenCanvas.getGraphics();
      }
      offScreenGraphics.setColor(getBackground());
      offScreenGraphics.fillRect(0,0,getSize().width,getSize().height);
      int top = shapes.size();
      for (int i = 0; i < top; i++) {
         Shape s = (Shape)shapes.elementAt(i);
         s.draw(offScreenGraphics);
      }
   }   
   
   public void itemStateChanged(ItemEvent evt) {
          // This is called to respond to item events.  Such events
          // can only be sent by the color choice menu,
          // so respond by setting the current color according to
          // the selected item in that menu.
      Choice colorChoice = (Choice)evt.getItemSelectable();
      switch (colorChoice.getSelectedIndex()) {
         case 0: currentColor = Color.red;     break;
         case 1: currentColor = Color.green;   break;
         case 2: currentColor = Color.blue;    break;
         case 3: currentColor = Color.cyan;    break;
         case 4: currentColor = Color.magenta; break;
         case 5: currentColor = Color.yellow;  break;
         case 6: currentColor = Color.black;   break;
         case 7: currentColor = Color.white;   break;
      }
   }
   
   public void actionPerformed(ActionEvent evt) {
          // Called to respond to action events.  The three shape-adding
          // buttons have been set up to send action events to this canvas.
          // Respond by adding the appropriate shape to the canvas.  This
          // also be a command from a pop-up menu.
      String command = evt.getActionCommand();
      if (command.equals("Rect"))
         addShape(new RectShape());
      else if (command.equals("Oval"))
         addShape(new OvalShape());
      else if (command.equals("RoundRect"))
         addShape(new RoundRectShape());
      else
         doPopupMenuCommand(command);
   }
   
   synchronized void addShape(Shape shape) {
          // Add the shape to the canvas, and set its size/position and color.
          // The shape is added at the top-left corner, with size 50-by-30.
          // Then redraw the canvas to show the newly added shape.
      shape.setColor(currentColor);
      shape.reshape(3,3,50,30);
      shapes.addElement(shape);
      repaint();
   }
   

   // ------------ This rest of the class implements dragging and the pop-up menu ---------------------
   
   PopupMenu popup;
   
   Shape selectedShape = null;     // This is null unless a menu has been popped up on this shape.

   Shape draggedShape = null;      // This is null unless a shape has been selected for dragging.

   int prevDragX;  // During dragging, these record the x and y coordinates of the
   int prevDragY;  //    previous position of the mouse.
   
   Shape clickedShape(int x, int y) {
         // Find the frontmost shape at coordinates (x,y); return null if there is none.
      for ( int i = shapes.size() - 1; i >= 0; i-- ) {  // check shapes from front to back
         Shape s = (Shape)shapes.elementAt(i);
         if (s.containsPoint(x,y))
            return s;
      }
      return null;
   }
   
      
   void doPopupMenuCommand(String command) {
         // Handle a command from the pop-up menu.
      if (selectedShape == null)  // should be impossible
         return;
      if (command.equals("Red"))
         selectedShape.setColor(Color.red);
      else if (command.equals("Green"))
         selectedShape.setColor(Color.green);
      else if (command.equals("Blue"))
         selectedShape.setColor(Color.blue);
      else if (command.equals("Cyan"))
         selectedShape.setColor(Color.cyan);
      else if (command.equals("Magenta"))
         selectedShape.setColor(Color.magenta);
      else if (command.equals("Yellow"))
         selectedShape.setColor(Color.yellow);
      else if (command.equals("Black"))
         selectedShape.setColor(Color.black);
      else if (command.equals("White"))
         selectedShape.setColor(Color.white);
      else if (command.equals("Big"))
         selectedShape.resize(75,45);
      else if (command.equals("Medium"))
         selectedShape.resize(50,30);
      else if (command.equals("Small"))
         selectedShape.resize(25,15);
      else if (command.equals("Delete"))
         shapes.removeElement(selectedShape);
      else if (command.equals("Bring To Front")) {
         shapes.removeElement(selectedShape);
         shapes.addElement(selectedShape);
      }
      repaint();
   }

   
   synchronized public void mousePressed(MouseEvent evt) {
         // User has pressed the mouse.  Find the shape that the user has clicked on, if
         // any.  If there is a shape at the position when the mouse was clicked, then
         // start dragging it.  If the user was holding down the shift key, then bring
         // the dragged shape to the front, in front of all the other shapes.
      int x = evt.getX();  // x-coordinate of point where mouse was clicked
      int y = evt.getY();  // y-coordinate of point 
      
      if (evt.isPopupTrigger()) {            // If this is a pop-up menu event that
         selectedShape = clickedShape(x,y);  // occurred over a shape, record which shape
         if (selectedShape != null)          // it is and show the menu.
            popup.show(this,x,y);
      }
      else {
         draggedShape = clickedShape(x,y);
         if (draggedShape != null) {
            prevDragX = x;
            prevDragY = y;
            if (evt.isShiftDown()) {                 // Bring the shape to the front by moving it to
               shapes.removeElement(draggedShape);  //       the end of the list of shapes.
               shapes.addElement(draggedShape);
               repaint();  // repaint canvas to show shape in front of other shapes
            }
         }
      }
   }
   
   synchronized public void mouseDragged(MouseEvent evt) {
          // User has moved the mouse.  Move the dragged shape by the same amount.
      if (draggedShape != null) {
         int x = evt.getX();
         int y = evt.getY();
         draggedShape.moveBy(x - prevDragX, y - prevDragY);
         prevDragX = x;
         prevDragY = y;
         repaint();      // redraw canvas to show shape in new position
      }
   }
   
   synchronized public void mouseReleased(MouseEvent evt) {
          // User has released the mouse.  Move the dragged shape, then set
          // shapeBeingDragged to null to indicate that dragging is over.
          // If the shape lies completely outside the canvas, remove it
          // from the list of shapes (since there is no way to ever move
          // it back onscreen).
      int x = evt.getX();
      int y = evt.getY();
      if (draggedShape != null) {
         draggedShape.moveBy(x - prevDragX, y - prevDragY);
         if ( draggedShape.left >= getSize().width || draggedShape.top >= getSize().height ||
                 draggedShape.left + draggedShape.width < 0 ||
                 draggedShape.top + draggedShape.height < 0 ) {  // shape is off-screen
            shapes.removeElement(draggedShape);  // remove shape from list of shapes
         }
         draggedShape = null;
         repaint();
      }
      else if (evt.isPopupTrigger()) {        // If this is a pop-up menu event that
         selectedShape = clickedShape(x,y);   // occurred over a shape, record the
         if (selectedShape != null)           // shape and show the menu.
            popup.show(this,x,y);
      }      
   }
   
   public void mouseEntered(MouseEvent evt) { }   // Other methods required for MouseListener and 
   public void mouseExited(MouseEvent evt) { }    //              MouseMotionListener interfaces.
   public void mouseMoved(MouseEvent evt) { }
   public void mouseClicked(MouseEvent evt) { }
   
}  // end class ShapeCanvas



abstract class Shape {

      // A class representing shapes that can be displayed on a ShapeCanvas.
      // The subclasses of this class represent particular types of shapes.
      // When a shape is first constucted, it has height and width zero
      // and a default color of white.

   int left, top;      // Position of top left corner of rectangle that bounds this shape.
   int width, height;  // Size of the bounding rectangle.
   Color color = Color.white;  // Color of this shape.
   
   void reshape(int left, int top, int width, int height) {
         // Set the position and size of this shape.
      this.left = left;
      this.top = top;
      this.width = width;
      this.height = height;
   }
   
   void resize(int width,int height) {
         // Set the size without changing the position
      this.width = width;
      this.height = height;
   }
   
   void moveTo(int x, int y) {
          // Move upper left corner to the point (x,y)
      this.left = x;
      this.top = y;
   }
   
   void moveBy(int dx, int dy) {
          // Move the shape by dx pixels horizontally and dy pixels veritcally
          // (by changing the position of the top-left corner of the shape).
      left += dx;
      top += dy;
   }
   
   void setColor(Color color) {
          // Set the color of this shape
      this.color = color;
   }

   boolean containsPoint(int x, int y) {
         // Check whether the shape contains the point (x,y).
         // By default, this just checks whether (x,y) is inside the
         // rectangle that bounds the shape.  This method should be
         // overridden by a subclass if the default behaviour is not
         // appropriate for the subclass.
      if (x >= left && x < left+width && y >= top && y < top+height)
         return true;
      else
         return false;
   }

   abstract void draw(Graphics g);  
         // Draw the shape in the graphics context g.
         // This must be overriden in any concrete subclass.

}  // end of class Shape



class RectShape extends Shape {
      // This class represents rectangle shapes.
   void draw(Graphics g) {
      g.setColor(color);
      g.fillRect(left,top,width,height);
      g.setColor(Color.black);
      g.drawRect(left,top,width,height);
   }
}


class OvalShape extends Shape {
       // This class represents oval shapes.
   void draw(Graphics g) {
      g.setColor(color);
      g.fillOval(left,top,width,height);
      g.setColor(Color.black);
      g.drawOval(left,top,width,height);
   }
   boolean containsPoint(int x, int y) {
         // Check whether (x,y) is inside this oval, using the
         // mathematical equation of an ellipse.
      double rx = width/2.0;   // horizontal radius of ellipse
      double ry = height/2.0;  // vertical radius of ellipse 
      double cx = left + rx;   // x-coord of center of ellipse
      double cy = top + ry;    // y-coord of center of ellipse
      if ( (ry*(x-cx))*(ry*(x-cx)) + (rx*(y-cy))*(rx*(y-cy)) <= rx*rx*ry*ry )
         return true;
      else
        return false;
   }
}


class RoundRectShape extends Shape {
       // This class represents rectangle shapes with rounded corners.
       // (Note that it uses the inherited version of the 
       // containsPoint(x,y) method, even though that is not perfectly
       // accurate when (x,y) is near one of the corners.)
   void draw(Graphics g) {
      g.setColor(color);
      g.fillRoundRect(left,top,width,height,width/3,height/3);
      g.setColor(Color.black);
      g.drawRoundRect(left,top,width,height,width/3,height/3);
   }
}

