/* 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); } }