
/* 
    A ShapeDrawFrame is a window to which the 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 version of the program also has a file menu containing
    a "New" command that clears all the shapes from the window,
    a "Save" command that saves the window data to a file, and
    an "Open" command that reads a file and restores the shape
    data from that file.  The program depends on the file
    MessageDialog.java.  The MessageDialog class is used in the
    doOpen() and doSave() subroutines.
    
    This program is based on a similar progrm, ShapDrawFrame.java,
    which lacked a file menu.
    
    David Eck
    August 19,  1998
*/


import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
import java.util.Vector;
import java.io.*;


public class ShapeDrawWithFiles extends Frame implements ActionListener {

   public static void main(String[] args) {
      new ShapeDrawWithFiles();
   }

   ShapeCanvas canvas;
   
   public ShapeDrawWithFiles() {  
        // 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 fileMenu = new Menu("File");  // file menu
      fileMenu.add("New");
      fileMenu.add("Save");
      fileMenu.add("Open");
      fileMenu.addActionListener(this);

      Menu shapeMenu = new Menu("Add Shape");  // shape creation menu
      shapeMenu.add("Rectangle");
      shapeMenu.add("Oval");
      shapeMenu.add("Round Rect");
      shapeMenu.addActionListener(this);  // frame object listens for menu commands

      Menu colorMenu = new Menu("Color");  // 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
      
      MenuBar mbar = new MenuBar();  // create a menu bar and add the two menus
      mbar.add(fileMenu);
      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) {
                           ShapeDrawWithFiles.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("New"))
         canvas.clear();
      else if (command.equals("Save"))
         doSave();
      else if (command.equals("Open"))
         doOpen();
      else 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());
   }
   
   void doSave() {
          // Respond to a "Save" command by saving the shape date from
          // the canvas to a file selected by the user via a file dialog.
      FileDialog fd = new FileDialog(this,"Save shapes to file",FileDialog.SAVE);
      fd.show();
      String file = fd.getFile();
      if (file != null) {  // if user did not cancel file dialog
         String dir = fd.getDirectory();
         try {
            File f = new File(dir,file);
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
            out.writeObject(canvas.getShapes());
            out.close();
         }
         catch (IOException e) {
            new MessageDialog(this,"Error while trying to write the file:  " + e.toString());
         }
      }
   }
   
   void doOpen() {
          // Respond to a "Open" command by reading shape date for
          // the canvas from a file selected by the user via a file dialog.
      FileDialog fd = new FileDialog(this,"Read shapes from file",FileDialog.LOAD);
      fd.show();
      String file = fd.getFile();
      Object obj;
      if (file != null) { // if user did not cancel file dialog
         String dir = fd.getDirectory();
         try {
            File f = new File(dir,file);
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(f));
            obj = in.readObject();
            in.close();
         }
         catch (IOException e) {
            new MessageDialog(this,"Error while trying to read the file:  " + e.toString());
            return;
         }
         catch (ClassNotFoundException e) {
            new MessageDialog(this,"Unexpected Data type found in file:  " + e.getMessage());
            return;
         }
         try {
            canvas.setShapes(obj);
         }
         catch (IllegalArgumentException e) {
            new MessageDialog(this,"File did not contain legal data for this program.");
         }
      }
   }
   
}  // 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();
   }
   
   void clear() {
         // remove all shapes
       shapes.setSize(0);
       repaint();
   }
   
   void setShapes(Object newShapes) throws IllegalArgumentException {
         // Replace current shapes with those in the parameter, shapes.
         // Throws IllegalArgumentException if shapes is not a vector of Shapes.
       if (newShapes == null || !(newShapes instanceof Vector))
          throw new IllegalArgumentException("Invalid data type.  Expecting list of shapes.");
       Vector v = (Vector)newShapes;
       for (int i = 0; i < v.size(); i ++)
          if (!(v.elementAt(i) instanceof Shape))
             throw new IllegalArgumentException("Invalid data type in shape list.");
       shapes = v;
       repaint();
    }
    
    Vector getShapes() {
       return shapes;
    }
   

   // ------------ 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 implements Serializable {

      // 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);
   }
}

