DiagramPanel.java

// Package name
package views.components;

//System imports
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

// Local imports
import core.ErrorHandler;
import core.UMLFileIO;
import model.Method;
import model.UMLClass;
import model.UMLRelationship;
import observe.Observable;
import observe.Observer;
import views.GUIView;
import views.components.testable.TestableMenu;
import views.components.testable.TestableMenuBar;
import views.components.testable.TestableMenuItem;
import views.components.testable.TestablePanel;
import views.components.testable.TestablePopupMenu;

/**
 * A JPanel to display the UMLClasses and relationships
 * @author 98% Ryan and a tiny bit of Dylan
 *
 */
public class DiagramPanel extends TestablePanel implements Observer, MouseListener, MouseMotionListener {
	private static final long serialVersionUID = 1L;

	// Instance of the Controller
	private GUIView view;
	
	// Map of graphical representations of classes
	private HashMap<String, GUIClass> guiClasses;

	// Location of the last class selected/dragged
	private int lastX;
	private int lastY;
	
	// Menus
	private JPopupMenu mouseMenu;
	private JPopupMenu classMenu;
	
	//Menu titles bar for testing main menu interaction
	private JMenuBar mainMenuBar;
	private JMenu mainFile; //save loading and exporting
	private JMenu mainActions; //everything else
	private JMenuItem mainRemoveClass;
	private JMenuItem mainEditClass;
	private JMenuItem mainAddField;
	private JMenuItem mainRemoveField;
	private JMenuItem mainEditField;
	private JMenuItem mainAddMethod;
	private JMenuItem mainRemoveMethod;
	private JMenuItem mainEditMethod;
	private JMenuItem mainAddRelationship;
	private JMenuItem mainRemoveRelationship;
	private JMenuItem mainAddClass;
	private JMenuItem mainSaveFile;
	private JMenuItem mainLoadFile;
	private JMenuItem mainExportPNG;
	private JMenuItem mainResize;
	
	// MenuItems for generic mouse menu
	private JMenuItem mouseAddClass;
	private JMenuItem mouseSaveFile;
	private JMenuItem mouseLoadFile;
	private JMenuItem mouseExportPNG;
	
	// MenuItems for class mouse menu
	private JMenuItem classRemoveClass;
	private JMenuItem classEditClass;
	private JMenuItem classAddField;
	private JMenuItem classRemoveField;
	private JMenuItem classEditField;
	private JMenuItem classAddMethod;
	private JMenuItem classRemoveMethod;
	private JMenuItem classEditMethod;
	private JMenuItem classAddRelationship;
	private JMenuItem classRemoveRelationship;
	
	// Mouse coordinates of the last time a right click was detected
	// 		as well as the last GUIClass that was selected, if one exists
	private int mouseX;
	private int mouseY;
	private GUIClass prev;
	
	/**
	 * Create a panel that allows components to be drawn and dragged by the user.
	 * Also allows drawing of relationship types.
	 * @param view- the parent view
	 * @param isHuman - whether the view is for a human or testing
	 */
	public DiagramPanel(GUIView view, boolean isHuman) {
		super(isHuman);
		setupOps(view);
	}
	
	private void setupOps(GUIView view) {
		this.view = view;
		
		// To enable dragging and dynamic locations, remove the layout manager
		setLayout(null);
		
		// Setup map of class names to guiClasses (very similar to class manager)
		guiClasses = new HashMap<String, GUIClass>();
		
		// Set default size
		setPreferredSize(new Dimension(600, 600));
		
		// Add component listener to set prefferedSize
		addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				setPreferredSize(getSize());
			}
		});
		
		// Add mouse listener for clicks
		addMouseListener(this);
		
		setupMenus();
		setupActions();
	}
	
	/**
	 * Override paint component so we can draw in the relationships
	 */
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		// Enable better 2D graphics
		Graphics2D g2d = (Graphics2D)g;
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		
		// Get the relationships and draw the relationship between the two classes
		HashMap<String, UMLRelationship> relations = view.getController().getModel().getRelationships();
		
		// Loop through relationships and draw the lines
		for(Map.Entry<String, UMLRelationship> entry : relations.entrySet()) {
			// Get the relationship
			UMLRelationship relation = entry.getValue();
			
			// Get each GUIClass
			GUIClass c1 = guiClasses.get(relation.getClass1().getName());
			GUIClass c2 = guiClasses.get(relation.getClass2().getName());
			
			// Check if line should be dashed
			if(relation.getType().toLowerCase().equals("realization")) {
		        BasicStroke dashed = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{4}, 0);
				g2d.setStroke(dashed);
			}
			else {
				g2d.setStroke(new BasicStroke(1));
			}
			
			// Check to see if the relationship is recursive or not
			//		Handle drawing differently
			
			// If not recursive then draw lines from midpoints of classes
			if(c1 != c2) {
				// Get center points of views
				int c1centerX = c1.getX() + c1.getWidth()/2;
				int c1centerY = c1.getY() + c1.getHeight()/2;
				int c2centerX = c2.getX() + c2.getWidth()/2;
				int c2centerY = c2.getY() + c2.getHeight()/2;
				
				// Draw horizontal line
				g2d.drawLine(c1centerX, c1centerY, c2centerX, c1centerY);
				
				// Draw vertical line
				g2d.drawLine(c2centerX, c1centerY, c2centerX, c2centerY);
				
				// Draw diamond/arrow
				int rectLen = 10;
				int rectX = c2centerX - rectLen/2;
				int rectY = 0;
				int angleRotate = 45;
				
				// Check how which way we need to rotate the rectangle and determine
				// 	where the location should really be based on the classes relative
				//	locations to each other
				if(Math.abs(c2centerY - c1centerY) < c2.getHeight()/2) {
					rectY = c2centerY - (c2centerY - c1centerY) - rectLen/2;
					if(c2centerX > c1centerX) {
						rectX = c2.getX() - rectLen;
						angleRotate += 90;
					}
					else if (c2centerX < c1centerX) {
						rectX = c2.getX() + c2.getWidth();
						angleRotate -= 90;
					}
				}
				else if(c2centerY < c1centerY)
					rectY = c2.getY() + c2.getHeight();
				else {
					rectY = c2.getY() - rectLen;
					angleRotate += 180;
				}
				
				// Rotate rectangle to create diamond shape
				AffineTransform old = g2d.getTransform();
				g2d.rotate(Math.toRadians(angleRotate), rectX + rectLen/2, rectY + rectLen/2);
				
				// Determine if we need to fill the rectangle or not
				if(relation.getType().toLowerCase().equals("composition"))
					g2d.fillRect(rectX, rectY, rectLen, rectLen);
				else if(relation.getType().equals("aggregation")) {
					g2d.setColor(Color.WHITE);
					g2d.fillRect(rectX, rectY, rectLen, rectLen);
					g2d.setColor(Color.BLACK);
					g2d.drawRect(rectX, rectY, rectLen, rectLen);
				}
				// Determine if we need dashed line
				else {
					g2d.setStroke(new BasicStroke(1));
					g2d.drawLine(rectX, rectY, rectX + rectLen, rectY);
					g2d.drawLine(rectX, rectY, rectX, rectY + rectLen);
				}
				
				// Reset the rotation
				g2d.setTransform(old);
			}
			// Otherwise do a loop in the shape of approximately:
			//     ------|
			//   __|__   |
			//   |    |  |
			//   |____|--|
			//   
			else {
				// How far loop is away from class border
				int offset = 20;
				
				int farX = c1.getX() + c1.getWidth() + offset;
				int farY = c1.getY() - offset;
				
				// Center points of view
				int centerX = c1.getX() + c1.getWidth()/2;
				int centerY = c1.getY() + c1.getHeight()/2;
				
				g2d.drawLine(centerX, centerY, centerX, farY);
				g2d.drawLine(centerX, farY, farX, farY);
				g2d.drawLine(farX, farY, farX, centerY);
				g2d.drawLine(farX, centerY, centerX, centerY);
				
				int rectLen = 10;
				int rectX = centerX - rectLen/2;
				int rectY = c1.getY() - rectLen;
				int angleRotate = 225;
				
				// Rotate rectangle
				AffineTransform old = g2d.getTransform();
				g2d.rotate(Math.toRadians(angleRotate), rectX + rectLen/2, rectY + rectLen/2);
				if(relation.getType().toLowerCase().equals("composition"))
					g2d.fillRect(rectX, rectY, rectLen, rectLen);
				else if(relation.getType().equals("aggregation")) {
					g2d.setColor(Color.WHITE);
					g2d.fillRect(rectX, rectY, rectLen, rectLen);
					g2d.setColor(Color.BLACK);
					g2d.drawRect(rectX, rectY, rectLen, rectLen);
				}
				else {
					g2d.setStroke(new BasicStroke(1));
					g2d.drawLine(rectX, rectY, rectX + rectLen, rectY);
					g2d.drawLine(rectX, rectY, rectX, rectY + rectLen);
				}
				g2d.setTransform(old);
			}
		}
	}
	
	/**
	 * Setup the mouse and window menus and their items.
	 * Set names for all components for tests
	 */
	private void setupMenus() {
		// Create the popup menu for when a user right clicks in the diagram
		mouseMenu = view.isHuman() ? new JPopupMenu() : new TestablePopupMenu();
		mouseMenu.setName("Mouse Menu");
		
		// Initialize the menu items
		mouseAddClass = createMenuItem("Add Class", "mouseAddClass");
		mouseSaveFile = createMenuItem("Save to File", "mouseSave");
		mouseLoadFile = createMenuItem("Load File", "mouseLoad");
		mouseExportPNG = createMenuItem("Export to PNG", "mouseExport");
		
		// Add menu items to mouse menu
		mouseMenu.add(mouseAddClass);
		mouseMenu.addSeparator();
		mouseMenu.add(mouseSaveFile);
		mouseMenu.add(mouseLoadFile);
		mouseMenu.add(mouseExportPNG);
		
		// Create the popup menu for when a user right clicks a class
		// 		NOTE: Displaying this is handled in the GUIView's mouse listeners
		classMenu = new JPopupMenu();
		classMenu.setName("Class Menu");
		
		// Initialize class menu options
		classRemoveClass = createMenuItem("Remove Class", "classRemoveClass");
		classEditClass = createMenuItem("Edit Class Name", "classEditClass");
		
		classAddField = createMenuItem("Add Field", "classAddField");
		classRemoveField = createMenuItem("Remove Field", "classRemoveField");
		classEditField = createMenuItem("Edit Field Name", "classEditField");

		classAddMethod = createMenuItem("Add Method", "classAddMethod");
		classRemoveMethod = createMenuItem("Remove Method", "classRemoveMethod");
		classEditMethod = createMenuItem("Edit Method Name", "classEditMethod");
		
		classAddRelationship = createMenuItem("Add Relationship", "classAddRelationship");
		classRemoveRelationship = createMenuItem("Remove Relationship", "classRemoveRelationship");
		
		// Add items to class menu
		classMenu.add(classRemoveClass);
		classMenu.add(classEditClass);
		classMenu.addSeparator();
		classMenu.add(classAddField);
		classMenu.add(classRemoveField);
		classMenu.add(classEditField);
		classMenu.addSeparator();
		classMenu.add(classAddMethod);
		classMenu.add(classRemoveMethod);
		classMenu.add(classEditMethod);
		classMenu.addSeparator();
		classMenu.add(classAddRelationship);
		classMenu.add(classRemoveRelationship);
		
		
		// Initialize the main menu items
		mainAddClass = createMenuItem("Add Class", "mainAddClass");
		mainSaveFile = createMenuItem("Save to File", "mainSave");
		mainLoadFile = createMenuItem("Load File", "mainLoad");
		mainExportPNG = createMenuItem("Export to PNG", "mainExport");
		mainResize = createMenuItem("Resize", "mainResize");
		
		//main Bar initialization
		mainMenuBar = view.isHuman() ? new JMenuBar() : new TestableMenuBar();
		mainMenuBar.setName("mainMenuBar");
		
		//main menu initialization
		if(view.isHuman()) {
			mainFile = new JMenu("File");
			mainActions = new JMenu("Actions");
		}
		else {
			mainFile = new TestableMenu();
			mainActions = new TestableMenu();
		}
		mainFile.setName("mainFile");
		mainActions.setName("mainActions");
		
		// Add menu items to mouse menu
		mainFile.add(mainAddClass);
		mainFile.addSeparator();
		mainFile.add(mainSaveFile);
		mainFile.add(mainLoadFile);
		mainFile.addSeparator();
		mainFile.add(mainExportPNG);
		mainFile.addSeparator();
		mainFile.add(mainResize);
		
		//main menu items initialization 
		mainRemoveClass = createMenuItem("Remove Class", "mainRemoveClass");
		mainEditClass = createMenuItem("Edit Class Name", "mainEditClass");
				
		mainAddField = createMenuItem("Add Field", "mainAddField");
		mainRemoveField = createMenuItem("Remove Field", "mainRemoveField");
		mainEditField = createMenuItem("Edit Field Name", "mainEditField");

		mainAddMethod = createMenuItem("Add Method", "mainAddMethod");
		mainRemoveMethod = createMenuItem("Remove Method", "mainRemoveMethod");
		mainEditMethod = createMenuItem("Edit Method Name", "mainEditMethod");
				
		mainAddRelationship = createMenuItem("Add Relationship", "mainAddRelationship");
		mainRemoveRelationship = createMenuItem("Remove Relationship", "mainRemoveRelationship");
		
		// Add items to mainActions
		mainActions.add(mainRemoveClass);
		mainActions.add(mainEditClass);
		mainActions.addSeparator();
		mainActions.add(mainAddField);
		mainActions.add(mainRemoveField);
		mainActions.add(mainEditField);
		mainActions.addSeparator();
		mainActions.add(mainAddMethod);
		mainActions.add(mainRemoveMethod);
		mainActions.add(mainEditMethod);
		mainActions.addSeparator();
		mainActions.add(mainAddRelationship);
		mainActions.add(mainRemoveRelationship);
		
		mainMenuBar.add(mainFile);
		mainMenuBar.add(mainActions);
		if(view.isHuman())
			view.getWindow().setJMenuBar(mainMenuBar);
	}
	
	/**
	 * Get a list of the valid relationship types
	 * @return String array of types
	 */
	private String[] validRelationships() {
		String[] relationships = {"aggregation", "composition", "inheritance", "realization"};
		return relationships;
	}
	
	
	/**
	 * Get a menu item with the specified label and set its name
	 * @param label - Text to display
	 * @param name - Name for testing purposes
	 */
	private JMenuItem createMenuItem(String label, String name) {
		JMenuItem temp;
		if(view.isHuman())
			temp = new JMenuItem(label);
		else
			temp = new TestableMenuItem();
		temp.setName(name);
		return temp;
	}
	
	/**
	 * Setup the actions for buttons
	 */
	private void setupActions() {
		// Setup actions for generic mouse menu items
		mouseAddClass.addActionListener(addClassAction());
		mouseSaveFile.addActionListener(saveFileAction());
		mouseLoadFile.addActionListener(loadFileAction());

		mainAddClass.addActionListener(addClassAction());
		mainSaveFile.addActionListener(saveFileAction());
		mainLoadFile.addActionListener(loadFileAction());
		mainExportPNG.addActionListener(exportPNGAction());
		mainResize.addActionListener(resizeAction());
		mouseExportPNG.addActionListener(exportPNGAction());
		
		// Setup actions for class menu items
		classRemoveClass.addActionListener(removeClassAction());
		classEditClass.addActionListener(editClassAction());
		mainRemoveClass.addActionListener(removeClassAction());
		mainEditClass.addActionListener(editClassAction());
		
		classAddField.addActionListener(addFieldAction());
		classRemoveField.addActionListener(removeFieldAction());
		classEditField.addActionListener(editFieldAction());
		mainAddField.addActionListener(addFieldAction());
		mainRemoveField.addActionListener(removeFieldAction());
		mainEditField.addActionListener(editFieldAction());
		
		
		classAddMethod.addActionListener(addMethodAction());
		classRemoveMethod.addActionListener(removeMethodAction());
		classEditMethod.addActionListener(editMethodAction());
		mainAddMethod.addActionListener(addMethodAction());
		mainRemoveMethod.addActionListener(removeMethodAction());
		mainEditMethod.addActionListener(editMethodAction());
		
		
		classAddRelationship.addActionListener(addRelationshipAction());
		classRemoveRelationship.addActionListener(removeRelationshipAction());
		mainAddRelationship.addActionListener(addRelationshipAction());
		mainRemoveRelationship.addActionListener(removeRelationshipAction());
		
	}
	
	/**
	 * Get an action listener that will add a class
	 * @return - ActionListener with definition for adding a class
	 */
	private ActionListener addClassAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Prompt user for class name
				Object className = view.promptInput("Enter class name:");
				
				// Make sure user did not cancel input
				if(className != null) {
					// Check if source is from the main menu, if so then offset the location
					if(e.getSource() == mainAddClass) {
						mouseX = 0;
						mouseY = 0;
						Component temp;
						while((temp = findComponentAt(mouseX, mouseY)) != null && temp != DiagramPanel.this) {
							mouseX += 10;
							mouseY += 10;
						}
					}
					int result = view.getController().addClass(className.toString(), mouseX, mouseY);
					if(result != 0)
						view.showError(DiagramPanel.this, result);
				}
				
				// Reset prev
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will remove a class
	 * @return - ActionListener with definition for removing a class
	 */
	private ActionListener removeClassAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				// Make sure a selected class exists
				if(prev == null || e.getSource() == mainRemoveField)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose avaliable class:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				int result = view.getController().removeClass(prev.getName());
				if(result != 0)
					view.showError(DiagramPanel.this, result);
				
				// Reset prev
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will edit a class's name
	 * @return - ActionListener with definition for editing a class name
	 */
	private ActionListener editClassAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {	
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainEditClass)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose avaliable class containing class:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				// Prompt the user for a new class name
				Object className = view.promptInput("Enter new class name:");
				
				// Make sure user did not cancel input
				if(className != null) {
					int result = view.getController().editClass(prev.getName(), className.toString());
					if(result != 0)
						view.showError(DiagramPanel.this, result);
				}
					
				// Reset prev
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will add a field to a given class
	 * @return - ActionListener with definition for adding a field
	 */
	private ActionListener addFieldAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainAddField)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose avaliable class to add field:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				// Prompt user for field type
				Object fieldType = view.promptInput("Enter field type (i.e. int, double, etc.): ");
				
				// Prompt user for field name
				Object fieldName = view.promptInput("Enter field name:");
				
				// Make sure user did not cancel input
				if(fieldName != null && fieldType != null) {
					int result = view.getController().addField(prev.getName(), fieldType.toString(), fieldName.toString());
					if(result != 0)
						view.showError(DiagramPanel.this, result);
				}
				
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will remove a field from a given class
	 * @return - ActionListener with definition for removing a field
	 */
	private ActionListener removeFieldAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainRemoveField)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose class containing field:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				// Get a list of the available field names to remove
				Object[] availableOptions = view.getController().getModel().getClass(prev.getName()).getFields().keySet().toArray();
				
				// Make sure there is at least one field
				if(availableOptions.length > 0) {
					Object fieldName = view.promptSelection("Choose field name:", availableOptions);
					// Make sure user didn't cancel input
					if(fieldName != null) {
						int result = view.getController().removeField(prev.getName(), fieldName.toString());
						if(result != 0)
							view.showError(DiagramPanel.this, result);
					}
				}
				
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will edit a field name from a given class
	 * @return - ActionListener with definition for editing the name of a field
	 */
	private ActionListener editFieldAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainEditField)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose class containing field:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				// Get a list of the available field names to edit
				Object[] availableOptions = view.getController().getModel().getClass(prev.getName()).getFields().keySet().toArray();
				
				// Make sure there is at least one field
				if(availableOptions.length > 0) {
					Object fieldName = view.promptSelection("Choose field name:", availableOptions);
					// Make sure user didn't cancel input
					if(fieldName != null) {
						// Prompt for the new name
						Object newFieldName = view.promptInput("Enter the new name of the field");
						
						// Make sure user didn't cancel
						if(newFieldName != null) {
							int result = view.getController().editField(prev.getName(), fieldName.toString(), newFieldName.toString());
							if(result != 0)
								view.showError(DiagramPanel.this, result);
						}
					}
				}
				
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will add a method to a given class
	 * @return - ActionListener with definition for adding a method
	 */
	private ActionListener addMethodAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainAddMethod)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose avaliable class:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				// Prompt user for return type
				Object returnType = view.promptInput("Enter return type:");
				
				// Prompt user for field name
				Object methodName = view.promptInput("Enter method name:");
				
				// Prompt user for argument list
				Object argList = view.promptInput("Parameters (separated by commas):");
				
				// Make sure user did not cancel input
				if(methodName != null && returnType != null && argList != null) {
					int result = view.getController().addMethod(prev.getName(), returnType.toString(),
							methodName.toString(), argList.toString());
					if(result != 0)
						view.showError(DiagramPanel.this, result);
				}
				
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will remove a method from a given class
	 * @return - ActionListener with definition for removing a method
	 */
	private ActionListener removeMethodAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainRemoveMethod)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose class containing method:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				// Get a list of the available field names to remove
				Object[] availableOptions = view.getController().getModel().getClass(prev.getName()).getMethods().keySet().toArray();
				
				// Make sure there is at least one field
				if(availableOptions.length > 0) {
					Object methodName = view.promptSelection("Choose method name:", availableOptions);
					// Make sure user didn't cancel input
					if(methodName != null) {
						Method method = view.getModel().getClass(prev.getName()).getMethods().get(methodName);
						// Make sure method exists
						if(method == null) {
							view.showError(DiagramPanel.this, ErrorHandler.setCode(406));
							return;
						}
						// Strip parameters
						String methodParams = method.getParams().substring(1, method.getParams().length()-1);
						int result = view.getController().removeMethod(prev.getName(), method.getName(), methodParams);
						if(result != 0)
							view.showError(DiagramPanel.this, result);
					}
				}
				
				prev = null;
			}
		};
	}
	
	
	/**
	 * Get an action listener that will edit a method name from a given class
	 * @return - ActionListener with definition for editing the name of a method
	 */
	private ActionListener editMethodAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainEditMethod)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose class containing method:", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}

				// Get a list of the available method names to edit
				Object[] availableOptions = view.getController().getModel().getClass(prev.getName()).getMethods().keySet().toArray();
				
				// Make sure there is at least one method
				if(availableOptions.length > 0) {
					Object methodName = view.promptSelection("Choose method name:", availableOptions);
					// Make sure user didn't cancel input
					if(methodName != null) {
						// Prompt for the new name
						Object newMethodName = view.promptInput("Enter the new name of the method");
						
						// Make sure user didn't cancel
						if(newMethodName != null) {
							Method method = view.getModel().getClass(prev.getName()).getMethods().get(methodName);
							// Strip parameters
							String methodParams = method.getParams().substring(1, method.getParams().length() -1);
							int result = view.getController().editMethod(prev.getName(), method.getName(), newMethodName.toString(), methodParams);
							if(result != 0)
								view.showError(DiagramPanel.this, result);
						}
					}
				}
				
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will add a relationship between two classes
	 * @return
	 */
	private ActionListener addRelationshipAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainAddRelationship)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose origin class: ", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}

				// Get a list of available second classes
				Object[] availableClasses = view.getController().getModel().getClassNames();
				
				// Make sure there is at least one other class
				if(availableClasses.length > 0) {
					// Get the destination class
					Object destClass = view.promptSelection("Destination class: ", availableClasses);
					// Get the type
					Object type = view.promptSelection("Relationship Type:", validRelationships());
					// Make sure user didn't cancel input
					if(destClass != null && type != null) {
						int result = view.getController().addRelationship(prev.getName(), type.toString(), destClass.toString());
						if(result != 0)
							view.showError(DiagramPanel.this, result);
					}
				}
				
				prev = null;
			}
		};
	}
	
	/**
	 * Get an action listener that will remove a relationship between two classes
	 * @return
	 */
	private ActionListener removeRelationshipAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Make sure at least one class exists
				if(view.getModel().getClassNames().length == 0)
					return;
				
				//listener for main menu buttons
				if(prev == null || e.getSource() == mainRemoveRelationship)
				{
					Object[] classChoices = view.getController().getModel().getClassNames();
					Object originClass = view.promptSelection("Choose origin class: ", classChoices);
					GUIClass temp = guiClasses.get(originClass);
					if (temp != null)
						prev = temp;
					else
					{
						ErrorHandler.setCode(109);
						return;	
					}
				}
				
				// Get a list of available second classes
				Object[] availableClasses = view.getController().getModel().getClassNames();
				
				// Make sure there is at least one field
				if(availableClasses.length > 0) {
					// Get the destination class
					Object destClass = view.promptSelection("Destination class: ", availableClasses);
					// Get the type
					Object type = view.promptSelection("Relationship Type: ", validRelationships());
					// Make sure user didn't cancel input
					if(destClass != null && type != null) {
						int result = view.getController().removeRelationship(prev.getName(), type.toString(), destClass.toString());
						if(result != 0)
							view.showError(DiagramPanel.this, result);
					}
				}
				
				prev = null;
			}
		};
	}

	/**
	 * Get an action listener that will open a save dialog box to save the file too
	 * @return
	 */
	private ActionListener saveFileAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Prompt user for file
				File saveFile = view.getFile("", "", "Save UNL", JFileChooser.SAVE_DIALOG);
				
				// Make sure user didn't close the console
				if(saveFile != null) {
					
					// Check if file name ends with '.json' and if not add it manually
					if(!saveFile.getPath().endsWith(".json"))
						saveFile = new File(saveFile.getAbsolutePath() + ".json");
					
					// Save the file
					UMLFileIO fileIO = new UMLFileIO();
					
					// Set the path
					int result = fileIO.setFile(saveFile.getAbsolutePath());
					if(result != 0) {
						view.showError(DiagramPanel.this, result);
						return;
					}
					
					// Write to file
					String json = view.getController().getModel().convertToJSON();
					result = fileIO.writeToFile(json);
					if(result != 0) {
						view.showError(DiagramPanel.this, result);
						return;
					}
				}
			}
		};
	}
	
	/**
	 * Get an action listener that will open a load dialog box to load the file from
	 * @return
	 */
	private ActionListener loadFileAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				
				// Prompt for file
				File loadFile = view.getFile("JSON", ".json", "Load File", JFileChooser.OPEN_DIALOG);
				
				// Make sure user didn't close the console
				if(loadFile != null) {
					
					// Open the file
					UMLFileIO fileIO = new UMLFileIO();
					
					// Set the path
					int result = fileIO.setFile(loadFile.getAbsolutePath());
					if(result != 0) {
						view.showError(DiagramPanel.this, result);
						return;
					}
					
					// Read from file
					Object[] res = fileIO.readFile();
					if((int)res[1] != 0) {
						view.showError(DiagramPanel.this, result);
						return;
					}
					
					// Load file
					result = view.getController().getModel().parseJSON((String)res[0]);
					if(result != 0) {
						view.showError(DiagramPanel.this, result);
						return;
					}
				
					// Manually add all classes to the GUI
					for(Object classNameObj : view.getController().getModel().getClassNames()) {
						String className = (String)classNameObj;
						// Forcefully call updated
						updated(null, "addClass", (Object)view.getController().getModel().getClass(className));
					}
					
					validate();
				}
			}
		};
	}
	
	/**
	 * Get an action listener that will export to a PNG file
	 * @return
	 */
	private ActionListener exportPNGAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Prompt user for file
				File exportFile = view.getFile("PNG", ".png", "Export to PNG", JFileChooser.SAVE_DIALOG);
				
				// Make sure the user didn't cancel the operation
				if(exportFile != null) {
					// Check if file name ends with '.png' and if not manually add it
					if(!exportFile.getPath().endsWith(".png")) {
						exportFile = new File(exportFile.getAbsolutePath() + ".png");
					}
					
					// Write screen to image
					// Temp image to copy the drawn screen to
					BufferedImage image = new BufferedImage(Math.max(getWidth(), 100), Math.max(getHeight(), 100), BufferedImage.TYPE_INT_RGB);
					// Graphics for the image
					Graphics2D graphics = image.createGraphics();
					// Copy component graphics to image graphics
					paintAll(graphics);
					// Export the image
					try {
						ImageIO.write(image, "png", exportFile);
					} catch(IOException er) {
						view.showError(DiagramPanel.this, 111);
					}
				}
			}
		};
	}
	
	/**
	 * Action to set the size of the UML Diagram
	 * @return
	 */
	private ActionListener resizeAction() {
		return new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				int newWidth, newHeight;

				// Prompt user for dimensions
				Object widthInput = view.promptInput("New width: ");
				// Make sure user didn't cancel
				if(widthInput == null)
					return;
				Object heightInput = view.promptInput("New height: ");
				// Make sure user didn't cancel
				if(heightInput == null)
					return;
				
				// Cast input as int
				try {
					newWidth = Integer.parseInt(widthInput.toString());
					newHeight = Integer.parseInt(heightInput.toString());
				} catch(NumberFormatException nfe) {
					view.showError(DiagramPanel.this, 112);
					return;
				}
				
				// Resize panel
				setPreferredSize(new Dimension(newWidth, newHeight));
				view.updateFrame();
			}
		};
	}

	/**
	 * Listen for changes from the model
	 */
	@Override
	public void updated(Observable src, String tag, Object data) {
		// Check the action and react accordingly
		if(tag.equals("addClass")) {
			// Cast data as UMLClass
			UMLClass umlClass = (UMLClass)data;
			
			// Create GUIClass representation
			GUIClass temp = view.isHuman() ? new GUIClass(umlClass, true) : new GUIClass(umlClass, false);
			// Add a mouse listener to allow dragging and different options
			temp.addMouseListener(this);
			temp.addMouseMotionListener(this);
			guiClasses.put(umlClass.getName(), temp);
			
			// Add GUIClass to panel
			add(temp);
		}
		else if(tag.equals("removeClass")) {
			// Find the GUIClass that correlates to the removed object and delete it
			UMLClass removed = (UMLClass)data;
			remove(guiClasses.get(removed.getName()));
			guiClasses.remove(removed.getName());
		}
		else if(tag.equals("fieldChange")) {
			// Cast data as UMLClass
			UMLClass umlClass = (UMLClass)data;
			
			// Update associated GUIClass data
			guiClasses.get(umlClass.getName()).updateFields();
		}
		else if(tag.equals("methodChange")) {
			// Cast data as UMLClass
			UMLClass umlClass = (UMLClass)data;
			
			// Update associated GUIClass data
			guiClasses.get(umlClass.getName()).updateMethods();
		}
		else if(tag.equals("relationshipChange")) {
			// Nothing to do
		}
		else if(tag.equals("classChange")) {
			// Update names
			// Because you can't edit the name field you have to remove and readd the class
			// But you can't remove/add in the middle of a loop so you have to readd all classes
			// Remove all classes
			guiClasses.forEach((k, v) -> remove(v));
			guiClasses.clear();
			// Add them back
			for(Object className : view.getModel().getClassNames()) {
				updated(null, "addClass", view.getModel().getClass((String)className));
			}
		}
		
		// Update display
		validate();
		repaint();
	}
	
	@Override
	public void mousePressed(MouseEvent e) {
		// Destroy last saved guiClass;
		prev = null;
		
		// Get the source of the mouse event
		Object source = e.getSource();
		
		// Check if press is a left click
		if(SwingUtilities.isLeftMouseButton(e)) {
			// Loop through GUI classes and check if it triggered the event
			for(Map.Entry<String, GUIClass> entry : guiClasses.entrySet()) {
				GUIClass guiClass = entry.getValue();
				
				if(source == guiClass) {
					lastX = e.getLocationOnScreen().x - guiClass.getX();
					lastY = e.getLocationOnScreen().y - guiClass.getY();
				
					break;
				}
			}
		}
		// Check if press is a right click
		else if(SwingUtilities.isRightMouseButton(e)) {
			// Check if the source is the diagram, if so show the generic mouse menu
			if(source == this) {
				mouseX = e.getX();
				mouseY = e.getY();
				mouseMenu.show(this, mouseX, mouseY);
			}
			// Otherwise check if the source was a GUI Class
			else if(source instanceof GUIClass) {
				// Cast mouse source to GUIClass
				GUIClass guiClass = (GUIClass)e.getSource();
				mouseX = e.getX();
				mouseY = e.getY();
				prev = guiClass;
				classMenu.show(guiClass, mouseX, mouseY);
			}
		}
	}
	
	@Override
	public void mouseDragged(MouseEvent e) {
		// Get the source of the mouse event
		Object source = e.getSource();
		
		// Check if press is a left click
		if(SwingUtilities.isLeftMouseButton(e)) {
			// Loop through GUI classes and check if it triggered the event
			for(Map.Entry<String, GUIClass> entry : guiClasses.entrySet()) {
				GUIClass guiClass = entry.getValue();
				
				if(source == guiClass) {
					// Set location of GUIClass
					int newX = e.getLocationOnScreen().x - lastX;
					int newY = e.getLocationOnScreen().y - lastY;
					guiClass.setLocation(newX, newY);
					
					lastX = e.getLocationOnScreen().x - guiClass.getX();
					lastY = e.getLocationOnScreen().y - guiClass.getY();
					
					// If the user is dragging a class then repaint in case the class has a relationship
					repaint();
				
					break;
				}
			}
		}
	}
	
	/**
	 * Find the component with the given name, or null if not found
	 * @param name
	 */
	public Component findComponent(String name) {
		// Check popup menus
		if(mouseMenu.getName().equals(name))
			return mouseMenu;
		if(classMenu.getName().equals(name))
			return classMenu;
		if(mainMenuBar.getName().equals(name))
			return mainMenuBar;
		
		// Check the main menu
		for(int i = 0; i < mainMenuBar.getMenuCount(); i++) {
			if(mainMenuBar.getMenu(i).getName().equals(name))
				return mainMenuBar.getMenu(i);
		}
		
		// Check main self children
		for(Component c : getComponents()) {
			if(c != null && c.getName().equals(name)) {
				return c;
			}
		}
		
		// Check menu children
		for(Component c : mouseMenu.getComponents()) {
			if(c != null && c.getName() != null && c.getName().equals(name)) {
				return c;
			}
		}
		for(Component c : classMenu.getComponents()) {
			if(c != null && c.getName() != null && c.getName().equals(name)) {
				return c;
			}
		}
		
		for(Component c : mainFile.getComponents()) {
			if(c != null && c.getName() != null && c.getName().equals(name)) {
				return c;
			}
		}
		for(Component c : mainActions.getComponents()) {
			if(c != null && c.getName() != null && c.getName().equals(name)) {
				return c;
			}
		}
		
		return null;
	}

	/**
	 * @return the guiClasses
	 */
	public HashMap<String, GUIClass> getGuiClasses() {
		return guiClasses;
	}

	@Override
	public void mouseClicked(MouseEvent arg0) {}

	@Override
	public void mouseEntered(MouseEvent arg0) {}
	
	@Override
	public void mouseExited(MouseEvent arg0) {}

	@Override
	public void mouseReleased(MouseEvent arg0) {}

	@Override
	public void mouseMoved(MouseEvent arg0) {}
}