
/* 
    A ShapeDrawFrame is a window to which teh user can add small colored
    shapes and then drag them around.  The shapes are rectangles,
    ovals, and roundrects.  The user adds a shape to the window by
    selecting an appropriate command from a "Add Shape" menu.  
    The shape is added at the upper left corner of the canvas.  
    The color of newly created shapes is controlled by a "Color" menu.
    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.  There is also a pop-up menu that appears
    when the user right-clicks a shape.  The pop-up menu can be 
    used to change the color or size of the shape, to delete
    the shape, and to bring the shape to the front.
    
    The ShapeDrawFrame class has a main() routine, and it can be
    run as an independent application.
    
    This file defines the frame class plus several other classes used
    by the applet, namely:  ShapeCanvas, Shape, RectShape, OvalShape,
    and RoundRectShape.
    
    This program is based on a similar applet, ShapDrawWithMenu.java,
    which was in turn based on a similar applet lacking a pop-up menu.
    
    David Eck
    August 12,  1998
*/


import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
import java.util.Vector;


public class ShapeDrawFrame extends Frame implements ActionListener {

   public static void main(String[] args) {
      new ShapeDrawFrame();
   }

   ShapeCanvas canvas;
   
   public ShapeDrawFrame() {  
        // Constructor.  Create and open the window.  The window has a menu bar
        // containing two menus, "Add Shape" and "Color".  The content of the
        // window is a canvas belonging to the class ShapeCanvas.
   
      super("Shape Draw");  // Set window title by calling the superclass constructor
   
      setBackground(Color.white);
      
      canvas = new ShapeCanvas();  // create the canvas
      add("Center",canvas);              // add canvas to frame

      Menu colorMenu = new Menu("Color", true);  // color choice menu
      colorMenu.add("Red");
      colorMenu.add("Green");
      colorMenu.add("Blue");
      colorMenu.add("Cyan");
      colorMenu.add("Magenta");
      colorMenu.add("Yellow");
      colorMenu.add("Black");
      colorMenu.add("White");
      colorMenu.addActionListener(this);  // frame object listens for menu commands
      
      Menu shapeMenu = new Menu("Add Shape", true);  // shape creation menu
      shapeMenu.add("Rectangle");
      shapeMenu.add("Oval");
      shapeMenu.add("Round Rect");
      shapeMenu.addActionListener(this);  // frame object listens for menu commands

      MenuBar mbar = new MenuBar();  // create a menu bar and add the two menus
      mbar.add(shapeMenu);
      mbar.add(colorMenu);
      
      setMenuBar(mbar); // add the menu bar to the frame
         
      setBounds(30,50,380,280);  // set the size and position of the window
      
      setResizable(false);       // make the window non-resizable
      
      addWindowListener(
                 new WindowAdapter() {  // add a listener that will close the window
                                        //     when the user clicks its close box
                        public void windowClosing(WindowEvent evt) {
                           ShapeDrawFrame.this.dispose();
                        }
                     }
           );  // end addWindowListener statement
      
      show();  // make the window visible
      
   }  // end constructor
   
   
   public void actionPerformed(ActionEvent evt) {
           // Respond to a command from one of the window's menu.
      String command = evt.getActionCommand();
      if (command.equals("Red"))
         canvas.currentColor = Color.red;
      else if (command.equals("Blue"))
         canvas.currentColor = Color.blue;
      else if (command.equals("Cyan"))
         canvas.currentColor = Color.cyan;
      else if (command.equals("Magenta"))
         canvas.currentColor = Color.magenta;
      else if (command.equals("Yellow"))
         canvas.currentColor = Color.yellow;
      else if (command.equals("Green"))
         canvas.currentColor = Color.green;
      else if (command.equals("Black"))
         canvas.currentColor = Color.black;
      else if (command.equals("White"))
         canvas.currentColor = Color.white;
      else if (command.equals("Rectangle"))
         canvas.addShape(new RectShape());
      else if (command.equals("Oval"))
         canvas.addShape(new OvalShape());
      else if (command.equals("Round Rect"))
         canvas.addShape(new RoundRectShape());
   }
   
}  // end class ShapeDrawFrame



class ShapeCanvas extends Canvas implements ActionListener,
                                            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.  The canvas object listenes for mouse events and
      // for commands from the pop-up menu.

   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);         // canvas will respond to mouse events
      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);  // canvas will respond to pop-up menu commands

   } // 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);
      }
   }   
      
   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;
   }
   
      
   public void actionPerformed(ActionEvent evt) {
         // Handle a command from the pop-up menu (Commands from the Frame's menus go to the Frame.)
      String command = evt.getActionCommand();
      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);
   }
}

