Index: /trunk/src/org/expeditee/actions/Actions.java
===================================================================
--- /trunk/src/org/expeditee/actions/Actions.java	(revision 6)
+++ /trunk/src/org/expeditee/actions/Actions.java	(revision 6)
@@ -0,0 +1,624 @@
+package org.expeditee.actions;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import org.expeditee.agents.Agent;
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.io.Conversion;
+import org.expeditee.io.Logger;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+
+/**
+ * The Action class is used to launch KMS Actions as well as launching JAGs.
+ * 
+ * This class checks all class files in the same directory, and reads in and
+ * adds all the methods from them. The methods are stored in a Hashtable so that
+ * the lowercase method names can be mapped to the correctly capatilized method
+ * names (to provide case-insensitivity)
+ */
+public class Actions {
+
+	// the currently running agent (if there is one)
+	private static Agent _Agent = null;
+
+	// maps lower case method names to the method
+	private static HashMap<String, Method> _Actions = new HashMap<String, Method>();
+
+	// maps lower case JAG class names to capitalized JAG names
+	private static HashMap<String, String> _JAGs = new HashMap<String, String>();
+
+	public static final String ROOT_PACKAGE = "org.expeditee.";
+	
+	// Package and class file locations
+	private static final String ACTIONS_PACKAGE = ROOT_PACKAGE + "actions.";
+
+	private static final String AGENTS_PACKAGE = ROOT_PACKAGE + "agents.";
+	
+	private static final String NAVIGATIONS_CLASS = ROOT_PACKAGE + "actions.NavigationActions";
+	
+	public static Class[] getClasses(String pckgname)
+			throws ClassNotFoundException {
+		ArrayList<Class> classes = new ArrayList<Class>();
+		// Get a File object for the package
+		File directory = null;
+		String path = pckgname.replace('.', '/');
+		try {
+			ClassLoader cld = Thread.currentThread().getContextClassLoader();
+			if (cld == null) {
+				throw new ClassNotFoundException("Can't get class loader.");
+			}
+			URL resource = cld.getResource(path);
+			if (resource == null) {
+				throw new ClassNotFoundException("No resource for " + path);
+			}
+			directory = new File(resource.getFile());
+		} catch (NullPointerException x) {
+			throw new ClassNotFoundException(pckgname + " (" + directory
+					+ ") does not appear to be a valid package");
+		}
+
+		int splitPoint = directory.getPath().indexOf('!');
+		if (splitPoint > 0) {
+			try {
+				String jarName = directory.getPath().substring(
+						"file:".length(), splitPoint);
+				// Windows HACK
+				if (jarName.indexOf(":") >= 0)
+					jarName = jarName.substring(1);
+
+				if (jarName.indexOf("%20") > 0) {
+					jarName = jarName.replace("%20", " ");
+				}
+
+				JarFile jarFile = new JarFile(jarName);
+
+				Enumeration entries = jarFile.entries();
+				while (entries.hasMoreElements()) {
+					ZipEntry entry = (ZipEntry) entries.nextElement();
+					String className = entry.getName();
+					if (className.startsWith(path)) {
+						if (className.endsWith(".class")
+								&& !className.contains("$")) {
+							classes.add(Class.forName(className.substring(0,
+									className.length() - 6).replace('/', '.')));
+						}
+					}
+				}
+				jarFile.close();
+
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+
+		} else {
+
+			if (directory.exists()) {
+				// Get the list of the files contained in the package
+				String[] files = directory.list();
+				for (int i = 0; i < files.length; i++) {
+					// we are only interested in .class files
+					if (files[i].endsWith(".class") && !files[i].contains("$")
+							&& !files[i].equals("Actions.class")) {
+						// removes the .class extension
+						classes
+								.add(Class.forName(pckgname
+										+ files[i].substring(0, files[i]
+												.length() - 6)));
+					}
+				}
+			} else {
+				throw new ClassNotFoundException(pckgname + " (" + directory
+						+ ") does not appear to be a valid package");
+			}
+		}
+		Class[] classesA = new Class[classes.size()];
+		classes.toArray(classesA);
+		return classesA;
+	}
+
+	/**
+	 * Clears out the Action and JAG Hashtables and refills them. Normally this
+	 * is only called once when the system starts.
+	 */
+	public static void Init() {
+		Class[] classes;
+		try {
+			classes = getClasses(AGENTS_PACKAGE);
+
+			for (int i = 0; i < classes.length; i++) {
+				String name = classes[i].getSimpleName();
+				// maps lower case name to correct capitalised name
+				_JAGs.put(name.toLowerCase(), name);
+			}
+		} catch (Exception e) {
+			System.out
+					.println("You must have Java 1.5 or higher to run Expeditee");
+			System.out.println(e.getMessage());
+		}
+		try {
+			classes = getClasses(ACTIONS_PACKAGE);
+
+			for (int i = 0; i < classes.length; i++) {
+				String name = classes[i].getSimpleName();
+				// read in all the methods from the class
+				try {
+					// System.out.println(name);
+					LoadMethods(Class.forName(ACTIONS_PACKAGE + name));
+				} catch (ClassNotFoundException e) {
+					Logger.Log(e);
+					e.printStackTrace();
+				}
+			}
+		} catch (Exception e) {
+			System.out.println(e.getMessage());
+		}
+	}
+
+	/**
+	 * Loads all the Methods that meet the requirements checked by MethodCheck
+	 * into the hashtable.
+	 * 
+	 * @param c
+	 *            The Class to load the Methods from.
+	 */
+	private static void LoadMethods(Class c) {
+		// list of methods to test
+		Method[] toLoad = c.getMethods();
+
+		for (Method m : toLoad) {
+			// only allow methods with the right modifiers
+			if (MethodCheck(m)) {
+				if (!(_Actions.containsKey(m.getName().toLowerCase())))
+					_Actions.put(m.getName().toLowerCase(), m);
+				else {
+					int i = 0;
+					while (_Actions.containsKey(m.getName().toLowerCase() + i))
+						i++;
+
+					_Actions.put(m.getName().toLowerCase() + i, m);
+				}
+
+			}
+		}
+	}
+
+	/**
+	 * Checks if the given Method corresponds to the restrictions of Action
+	 * commands, namely: Declared (not inherited), Public, and Static, with a
+	 * void return type.
+	 * 
+	 * @param m
+	 *            The Method to check
+	 * @return True if the Method meets the above conditions, false otherwise.
+	 */
+	private static boolean MethodCheck(Method m) {
+		// the return type must be void
+		if (m.getReturnType() != void.class)
+			return false;
+
+		int mods = m.getModifiers();
+
+		// check the method is declared (not inherited)
+		if ((mods & Method.DECLARED) != Method.DECLARED)
+			return false;
+
+		// check the method is public
+		if ((mods & Modifier.PUBLIC) != Modifier.PUBLIC)
+			return false;
+
+		// check the method is static
+		if ((mods & Modifier.STATIC) != Modifier.STATIC)
+			return false;
+
+		// if we have not returned yet, then the tests have all passed
+		return true;
+	}
+
+	/**
+	 * Performs the given KMS action command. The source Frame and Item are
+	 * given because they are required by some actions. Note that the source
+	 * frame does not have to be the Item's parent Frame.
+	 * 
+	 * @param source
+	 *            The Frame that the action should apply to
+	 * @param launcher
+	 *            The Item that has the action assigned to it
+	 * @param command
+	 *            The action to perform
+	 */
+	public static void PerformAction(Frame source, Item launcher, String command) {
+		// separate method name and parameter names
+		String mname = getName(command);
+		if (command.length() > mname.length())
+			command = command.substring(mname.length() + 1);
+		else
+			command = "";
+
+		// Strip off the @ from annotation items
+		if (mname.startsWith("@"))
+			mname = mname.substring(1);
+
+		mname = mname.trim().toLowerCase();
+
+		// check for protection on frame
+		if (ItemUtils.ContainsTag(source.getItems(), "@No" + mname)) {
+			FrameGraphics.DisplayMessage("Frame is protected by @No" + mname
+					+ " tag.");
+			return;
+		}
+
+		// retrieve methods that match the name
+		Method toRun = _Actions.get(mname);
+
+		// if this is not the name of a method, it may be the name of an agent
+		if (toRun == null) {
+			LaunchAgent(mname, command, source);
+			return;
+		}
+
+		// Need to save the frame if we are navigating away from it so we dont
+		// loose changes
+		if (toRun.getDeclaringClass().getName().equals(
+				 NAVIGATIONS_CLASS))
+			FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
+
+		// if there are duplicate methods with the same name
+		List<Method> possibles = new LinkedList<Method>();
+		possibles.add(toRun);
+		int i = 0;
+		while (_Actions.containsKey(mname + i)) {
+			possibles.add(_Actions.get(mname + i));
+			i++;
+		}
+
+		for (Method possible : possibles) {
+
+			// try first with the launching item as a parameter
+
+			// run method
+			try {
+				// convert parameters to objects and get the method to invoke
+				Object[] parameters = CreateObjects(possible, source, launcher,
+						command);
+
+				possible.invoke(null, parameters);
+				return;
+			} catch (Exception e) {
+				Logger.Log(e);
+				// e.printStackTrace();
+			}
+		}
+	}
+
+	/**
+	 * Launches an agent with the given name, and passes in the given parameters
+	 * 
+	 * @param name
+	 *            The name of the JAG to load
+	 * @param parameters
+	 *            The parameters to pass to the JAG
+	 * @param source
+	 *            The starting Frame that the JAG is being launched on
+	 */
+	private static void LaunchAgent(String name, String parameters, Frame source) {
+		// save the current frame (if necesssary)
+		FrameUtils.LeavingFrame(source);
+
+		try {
+			// check for stored capitalisation
+			if (_JAGs.containsKey(name.toLowerCase())) {
+				name = _JAGs.get(name.toLowerCase());
+			} else if (name.toLowerCase().endsWith("tree")) {
+				parameters = name.substring(0, name.length() - "tree".length())
+						+ " " + parameters;
+				name = "WriteTree";
+			} else if (name.toLowerCase().endsWith("frame")) {
+				parameters = name
+						.substring(0, name.length() - "frame".length())
+						+ " " + parameters;
+				name = "WriteFrame";
+			}
+
+			// load the JAG class
+			Class agentClass = Class.forName(AGENTS_PACKAGE + name);
+
+			// get the constructor for the JAG class
+			Constructor con = null;
+			Constructor constructors[] = agentClass.getConstructors();
+			Object[] params = null;
+
+			// determine correct parameters for constructor
+			for (Constructor c : constructors) {
+				if (parameters.length() > 0
+						&& c.getParameterTypes().length == 1) {
+					con = c;
+					params = new String[1];
+					params[0] = parameters;
+					break;
+				} else if (c.getParameterTypes().length == 0 && con == null)
+					con = c;
+			}
+
+			// if there is no constructor, return
+			if (con == null) {
+				FrameGraphics.DisplayMessage("Invalid parametres for agent.");
+				//System.out.println("Constructor not found...");
+				return;
+			}
+
+			// create the JAG
+			_Agent = (Agent) con.newInstance(params);
+
+			Thread t = new Thread(_Agent);
+			t.setPriority(Thread.MIN_PRIORITY);
+
+			// check for errors during initialisation
+			if (!_Agent.initialise(source)) {
+				FrameGraphics.ErrorMessage("Error initialising agent: " + name);
+				return;
+			}
+
+			_Agent.setStartFrame(source);
+
+			t.start();
+
+			// if there is a completion frame, then display it to the user
+			if (_Agent.hasResultFrame()) {
+				Frame next = _Agent.getResultFrame();
+				FrameUtils.DisplayFrame(next, true);
+			}
+
+		} catch (ClassNotFoundException cnf) {
+			FrameGraphics.ErrorMessage("Error: '" + name
+					+ "' is not an action statement or Agent.");
+		} catch (Exception e) {
+			FrameGraphics.ErrorMessage("Error creating Agent: '" + name + "'");
+			System.out.println("Agent set to Null.");
+			_Agent = null;
+			e.printStackTrace();
+			Logger.Log(e);
+		}
+
+		return;
+	}
+
+	/**
+	 * Used to determine if the previously launched agent is still executing.
+	 * 
+	 * @return True if the last Agent is still executing, False otherwise.
+	 */
+	public static boolean isAgentRunning() {
+		if (_Agent != null)
+			return _Agent.isRunning();
+
+		return false;
+	}
+
+	/**
+	 * Stops the currently running Agent (If there is one) by calling
+	 * Agent.stop(). Note: This may not stop the Agent immediately, but the
+	 * Agent should terminate as soon as it is safe to do so.
+	 */
+	public static void stopAgent() {
+		if (_Agent != null) {
+			FrameGraphics.DisplayMessage("Stopping Agent...");
+			_Agent.stop();
+
+			// while (_Agent.isRunning())
+			// ;
+
+			// FrameGraphics.DisplayMessage("Agent Stopped.");
+		}
+	}
+
+	public static void interruptAgent() {
+		if (_Agent != null) {
+			_Agent.interrupt();
+		}
+	}
+
+	/**
+	 * Takes a list of Methods and finds one that accepts the given list of
+	 * objects as parameters
+	 * 
+	 * @param possibleMethods
+	 *            The list of Methods to search through
+	 * @param parameters
+	 *            The array of parameters that the Method should accept
+	 * @return The Method found if one was found, otherwise null
+	 */
+	/*
+	 * private static Method FindCompatibleMethods(List<Method>
+	 * possibleMethods, Object[] parameters){ Method toRun = null;
+	 * 
+	 * for(Method m : possibleMethods){ //the number of parameters must be the
+	 * same if(parameters.length == m.getParameterTypes().length){ //if no
+	 * methods are required, there is nothing else to check if(parameters.length ==
+	 * 0) toRun = m; else //check that the list of parameters are of the right
+	 * types for(int i = 0; i < parameters.length; i++){
+	 * if(m.getParameterTypes()[i].isInstance(parameters[i])) toRun = m; else
+	 * toRun = null; }
+	 * 
+	 * //if a method has been found already, there is no need to keep searching
+	 * if(toRun != null) break; } }
+	 * 
+	 * return toRun; }
+	 */
+
+	/**
+	 * Converts the given String of values into an array of Objects
+	 * 
+	 * @param launcher
+	 *            The Item used to launch the action, it may be required as a
+	 *            parameter
+	 * @param values
+	 *            A list of space separated String values to convert to objects
+	 * @return The created array of Objects
+	 */
+	public static Object[] CreateObjects(Method method, Frame source,
+			Item launcher, String values) {
+		// The parameter types that should be created from the given String
+		Class[] paramTypes = method.getParameterTypes();
+
+		// if the method has no parameters
+		if (paramTypes.length == 0)
+			return new Object[0];
+
+		Object[] objects = new Object[paramTypes.length];
+		int ind = 0;
+
+		// if the first class in the list is a frame or item, it is the source
+		// or launcher
+		// length must be at least one if we are still running
+		if (paramTypes[0] == Frame.class) {
+			objects[0] = source;
+			ind = 1;
+		} else if (paramTypes[0] == Item.class) {
+			objects[0] = launcher;
+			ind = 1;
+		}
+
+		// if the first class was a frame or item, the second class might be the
+		// other
+		if (paramTypes.length > 1 && objects[0] != null) {
+			if (paramTypes[1] == Frame.class) {
+				objects[1] = source;
+				ind = 2;
+			} else if (paramTypes[1] == Item.class) {
+				objects[1] = launcher;
+				ind = 2;
+			}
+		}
+
+		String param;
+		// convert the rest of the objects
+		for (; ind < objects.length; ind++) {
+			// strip off the next value
+			param = ParseValue(values);
+			values = RemainingParams(values);
+
+			// check if its the last param and combine
+			if (values.length() > 0 && ind == objects.length - 1)
+				param = param.trim() + ' ' + values.trim();
+
+			// convert the value to an object
+			Object o = Conversion.Convert(paramTypes[ind], param);
+			objects[ind] = o;
+		}
+
+		return objects;
+
+		// strip off the first parameter
+
+		/*
+		 * //the list of objects to return List<Object> returnParams = new
+		 * ArrayList<Object>(); if(launcher != null)
+		 * returnParams.add(launcher);
+		 * 
+		 * while(param != null){ /* //the parameter is a String
+		 * if(param.charAt(0) == '"') returnParams.add(param.substring(1,
+		 * param.length() - 1)); else{ //try to parse an int try{ int val =
+		 * Integer.parseInt(param); returnParams.add(val);
+		 * }catch(NumberFormatException nfe){ System.out.println("Type not found
+		 * for: " + param); } }
+		 * 
+		 * if(param.length() < values.length()) values =
+		 * values.substring(param.length() + 1); else values = "";
+		 */
+
+		// returnParams.add(KMSConversion.Convert(type, value))
+		// get the next value
+		/*
+		 * param = ParseValue(values); }
+		 * 
+		 * return returnParams.toArray();
+		 */
+	}
+
+	/**
+	 * Returns a string containing the remaining params after ignoring the first
+	 * one.
+	 * 
+	 * @param params
+	 *            a space sparated list of N parameters
+	 * @return the remaining N - 1 parameters
+	 */
+	private static String RemainingParams(String params) {
+		if (params.length() == 0)
+			return null;
+
+		// remove leading and trailing spaces
+		params = params.trim();
+
+		// if there are no more parameters, we are done
+		if (params.indexOf(" ") < 0) {
+			return "";
+		}
+
+		// Check if we have a string parameter
+		if (params.charAt(0) == '"') {
+			return params.substring(params.indexOf('"', 1) + 1);
+		}
+
+		return params.substring(params.indexOf(" "));
+	}
+
+	/**
+	 * Returns the first value in the space separated String of parameters
+	 * passed in. Strings are enclosed in double quotes.
+	 * 
+	 * @param params
+	 *            The String of space separated values
+	 * @return The first value in the String
+	 */
+	private static String ParseValue(String params) {
+		if (params.length() == 0)
+			return null;
+
+		// remove leading and trailing spaces
+		String param = params.trim();
+
+		// if there are no more parameters, we are done
+		if (param.indexOf(" ") < 0) {
+			return param;
+		}
+
+		// Check if we have a string parameter
+		if (param.charAt(0) == '"') {
+			return param.substring(1, param.indexOf('"', 1));
+		}
+
+		return param.substring(0, param.indexOf(" "));
+	}
+
+	/**
+	 * Separates the name of the given command from any parameters and returns
+	 * them
+	 * 
+	 * @param command
+	 *            The String to separate out the Action or Agent name from
+	 * @return The name of the Action of Agent with parameters stripped off
+	 */
+	private static String getName(String command) {
+		if (command.indexOf(" ") < 0)
+			return command;
+
+		return command.substring(0, command.indexOf(" "));
+	}
+}
Index: /trunk/src/org/expeditee/actions/Debug.java
===================================================================
--- /trunk/src/org/expeditee/actions/Debug.java	(revision 6)
+++ /trunk/src/org/expeditee/actions/Debug.java	(revision 6)
@@ -0,0 +1,45 @@
+package org.expeditee.actions;
+
+import java.util.List;
+
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.items.Constraint;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+
+/**
+ * This class is used for temporary debugging actions that will get removed from
+ * the final product.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Debug {
+
+	/**
+	 * Outputs the list of constraints all Items in the current Frame have.
+	 */
+	public static void ShowConstraints() {
+		List<Item> items = DisplayIO.getCurrentFrame().getItems();
+
+		for (Item i : items)
+			if (i instanceof Dot) {
+				System.out.println(i.getID());
+
+				for (Constraint c : ((Dot) i).getConstraints())
+					if (c.getOppositeEnd((Dot) i) != null)
+						System.out.println("\t"
+								+ c.getOppositeEnd((Dot) i).getID());
+					else
+						System.out.println("\tNULL");
+
+				System.out.println("------------");
+			}
+
+		System.out.println("==================");
+	}
+
+	public static void PrintLine() {
+		System.out.println("Action");
+	}
+}
Index: /trunk/src/org/expeditee/actions/Misc.java
===================================================================
--- /trunk/src/org/expeditee/actions/Misc.java	(revision 6)
+++ /trunk/src/org/expeditee/actions/Misc.java	(revision 6)
@@ -0,0 +1,462 @@
+package org.expeditee.actions;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GraphicsEnvironment;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameMouseActions;
+import org.expeditee.items.Item;
+import org.expeditee.items.Text;
+import org.expeditee.stats.SessionStats;
+import org.expeditee.stats.StatsLogger;
+
+/**
+ * A list of miscellaneous KMS Actions and Actions specific to Expeditee
+ * 
+ */
+public class Misc {
+	private static final int STATS_FONT_SIZE = 18;
+
+	private static final int FRAME_FILE_FONT_SIZE = 10;
+
+	/**
+	 * Causes the system to beep
+	 */
+	public static void Beep() {
+		java.awt.Toolkit.getDefaultToolkit().beep();
+	}
+
+	/**
+	 * Forces a repaint of the current Frame
+	 */
+	public static void Display() {
+		FrameGraphics.ForceRepaint();
+	}
+
+	/**
+	 * Restores the current frame to the last saved version currently on the
+	 * hard disk
+	 */
+	public static void Restore() {
+		FrameIO.Reload();
+
+		FrameGraphics.DisplayMessage("Restoration complete.");
+	}
+
+	/**
+	 * Toggles AudienceMode on or off
+	 */
+	public static void ToggleAudienceMode() {
+		FrameGraphics.ToggleAudienceMode();
+	}
+
+	/**
+	 * Toggles TwinFrames mode on or off
+	 */
+	public static void ToggleTwinFramesMode() {
+		DisplayIO.ToggleTwinFrames();
+	}
+
+	/**
+	 * Exits the System
+	 */
+	public static void Exit() {
+		/**
+		 * TODO: Prompt the user etc.
+		 */
+		FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
+
+		StatsLogger.WriteStatsFile();
+
+		System.exit(0);
+	}
+
+	/**
+	 * If the given Item is a Text Item, then the text of the Item is
+	 * interpreted as actions, if not this method does nothing.
+	 * 
+	 * @param current
+	 *            The Item to read the Actions from
+	 */
+	public static void RunCurrentItem(Item current) {
+		if (current instanceof Text) {
+			List<String> actions = ((Text) current).getText();
+
+			for (String action : actions) {
+				Actions.PerformAction(DisplayIO.getCurrentFrame(), current,
+						action);
+			}
+		}
+
+	}
+
+	/**
+	 * Prompts the user to confirm deletion of the current Frame, and deletes if
+	 * the user chooses. After deletion this action calls back(), to ensure the
+	 * deleted frame is not still being shown
+	 * 
+	 */
+	public static void DeleteFrame() {
+		Frame toDelete = DisplayIO.getCurrentFrame();
+		DisplayIO.Back();
+		try {
+			String deletedFrame = toDelete.getFrameName();
+			boolean del = FrameIO.DeleteFrame(toDelete);
+			if (!del) {
+				FrameGraphics.ErrorMessage("Error trying to delete "
+						+ toDelete.getFrameName());
+			} else {
+				Frame current = DisplayIO.getCurrentFrame();
+				for (Item i : current.getItems())
+					if (i.getLink() != null
+							&& i.getLink().toLowerCase().equals(
+									toDelete.getFrameName().toLowerCase())) {
+						i.setLink(null);
+					}
+
+				FrameGraphics.Repaint();
+				FrameGraphics.DisplayMessage(deletedFrame
+						+ " Deleted.");
+			}
+		} catch (IOException ioe) {
+			FrameGraphics.ErrorMessage("Error trying to delete "
+					+ toDelete.getFrameName() + ":\n" + ioe.getMessage());
+		}
+	}
+
+	/**
+	 * Loads the Frame linked to by the given Item. The first Item on the Frame
+	 * that is not the title or name is then placed on the cursor. If the given
+	 * Item has no link, or no item is found then this is a no-op.
+	 * 
+	 * @param current
+	 *            The Item that links to the Frame that the Item will be loaded
+	 *            from.
+	 */
+	public static void GetItemFromChildFrame(Item current) {
+		Item item = getFirstBodyItemOnChildFrame(current);
+		// if no item was found
+		if (item == null) {
+			return;
+		}
+
+		// copy the item and switch
+		item = item.copy();
+		item.setPosition(DisplayIO.getMouseX(), DisplayIO.getMouseY());
+		item.setParent(null);
+
+		FrameMouseActions.pickup(item);
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Sets the given Item to have the Given Color. Color can be null (for
+	 * default)
+	 * 
+	 * @param toChange
+	 *            The Item to set the Color.
+	 * @param toUse
+	 *            The Color to give the Item.
+	 */
+	public static void SetCurrentItemBackgroundColor(Item toChange, Color toUse) {
+		if (toChange == null)
+			return;
+
+		toChange.setBackgroundColor(toUse);
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Sets the given Item to have the Given Color. Color can be null (for
+	 * default)
+	 * 
+	 * @param toChange
+	 *            The Item to set the Color.
+	 * @param toUse
+	 *            The Color to give the Item.
+	 */
+	public static void SetCurrentItemColor(Item toChange, Color toUse) {
+		if (toChange == null)
+			return;
+
+		toChange.setColor(toUse);
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Creates a new Text Object containing the Stats that are in the the
+	 * JFrame's Title bar, The newly created Text Object is then attached to the
+	 * cursor via FrameMouseActions.pickup(Item)
+	 */
+	public static void GetCurrentStats() {
+		String stats = SessionStats.getCurrentStats();
+
+		Text text = DisplayIO.getCurrentFrame().createNewText();
+		// We dont want the stats to wrap at all
+		text.setMaxSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
+
+		text.setText(stats);
+
+		text.setPosition(DisplayIO.getMouseX(), DisplayIO.getMouseY());
+		text.setSize(STATS_FONT_SIZE);
+
+		FrameMouseActions.pickup(text);
+
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Creates a new Text Object containing the contents of the current frames
+	 * file.
+	 */
+	public static void GetCurrentFrameFile() {
+
+		String fileContents = FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
+
+		Text text = DisplayIO.getCurrentFrame().createNewText();
+		// We dont want the stats to wrap at all
+		text.setMaxSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
+
+		text.setText(fileContents);
+
+		text.setPosition(DisplayIO.getMouseX(), DisplayIO.getMouseY());
+		text.setSize(FRAME_FILE_FONT_SIZE);
+
+		FrameMouseActions.pickup(text);
+
+		FrameGraphics.Repaint();
+	}
+	
+	/**
+	 * Creates a new Text Object containing the available fonts.
+	 */
+	public static void GetAvailableFontFamilyNames() {
+
+		String[] availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
+		StringBuilder fontsList = new StringBuilder();
+		for (String s: availableFonts) {
+			fontsList.append(s).append('\n');
+		}
+		
+		Text text = DisplayIO.getCurrentFrame().createNewText();
+		// We dont want the stats to wrap at all
+		text.setMaxSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
+		text.setText(fontsList.toString());
+
+		text.setPosition(DisplayIO.getMouseX(), DisplayIO.getMouseY());
+		text.setSize(FRAME_FILE_FONT_SIZE);
+
+		FrameMouseActions.pickup(text);
+
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Resets the statistics back to zero.
+	 */
+	public static void ResetStats() {
+		StatsLogger.WriteStatsFile();
+		SessionStats.resetStats();
+	}
+
+	/**
+	 * Loads a frame with the given name and saves it as a JPEG image.
+	 * 
+	 * @param framename
+	 *            The name of the Frame to save
+	 */
+	public static void JpegFrame(String framename) {
+		ImageFrame(framename, "JPG");
+	}
+
+	/**
+	 * Saves the current frame as a JPEG image. This is the same as calling
+	 * JpegFrame(currentFrame.getFrameName())
+	 */
+	public static void JpegFrame() {
+		ImageFrame(DisplayIO.getCurrentFrame().getFrameName(), "JPG");
+	}
+
+	/**
+	 * Loads a frame with the given name and saves it as a PNG image.
+	 * 
+	 * @param framename
+	 *            The name of the Frame to save
+	 */
+	public static void PNGFrame(String framename) {
+		ImageFrame(framename, "PNG");
+	}
+
+	/**
+	 * Saves the current frame as a PNG image. This is the same as calling
+	 * PNGFrame(currentFrame.getFrameName())
+	 */
+	public static void PNGFrame() {
+		ImageFrame(DisplayIO.getCurrentFrame().getFrameName(), "PNG");
+	}
+
+	/**
+	 * Saves the Frame with the given Framename as an image of the given format.
+	 * 
+	 * @param framename
+	 *            The name of the Frame to save as an image
+	 * @param format
+	 *            The Image format to use (i.e. "PNG", "BMP", etc)
+	 */
+	public static void ImageFrame(String framename, String format) {
+		Frame loaded = FrameIO.LoadFrame(framename);
+		String fileName = loaded.getExportFileName();
+
+		// if the frame was loaded successfully
+		if (loaded != null) {
+			// check if the buffer needs to be redrawn
+			// if (loaded.getBuffer() == null)
+			FrameGraphics.UpdateBuffer(loaded, false);
+
+			BufferedImage screen = loaded.getBuffer().getSnapshot();
+
+			try {
+				// set up the file for output
+				File out = new File(FrameIO.EXPORTS_DIR + fileName + "."
+						+ format.toLowerCase());
+				if (!out.getParentFile().exists())
+					out.mkdirs();
+
+				ImageIO.write(screen, format, out);
+				FrameGraphics.DisplayMessage("Frame successfully saved to "
+						+ FrameIO.EXPORTS_DIR + out.getName());
+			} catch (IOException e) {
+				FrameGraphics.ErrorMessage(e.getMessage());
+			}
+			// if the frame was not loaded successfully, alert the user
+		} else
+			FrameGraphics.DisplayMessage("Frame '" + framename
+					+ "' could not be found.");
+	}
+
+	/**
+	 * Displays a message in the message box area.
+	 * 
+	 * @param message
+	 *            the message to display
+	 */
+	public static void MessageLn(String message) {
+		FrameGraphics.DisplayMessage(message);
+	}
+
+	public static void MessageLn2(String message, String message2) {
+		FrameGraphics.DisplayMessage(message + " " + message2);
+	}
+
+	public static void CopyFile(String existingFile, String newFileName) {
+		try {
+			//TODO is there a built in method which will do this faster?
+			
+			FrameGraphics.DisplayMessage("Copying file " + existingFile + " to " + newFileName + "...");
+			FileInputStream is = new FileInputStream(existingFile);
+			FileOutputStream os = new FileOutputStream(
+					newFileName, false);
+			int data;
+			while ( (data = is.read()) != -1 ) {
+				os.write(data);
+			}
+			os.flush();
+			os.close();
+			is.close();
+			FrameGraphics.DisplayMessage("File copied successfully");
+		} catch (FileNotFoundException e) {
+			FrameGraphics.DisplayMessage("Error opening file: " + existingFile);
+		} catch (Exception e){
+			FrameGraphics.DisplayMessage("File could not be copied");
+		}
+	}
+
+	/**
+	 * Loads the Frame linked to by the given Item. The first Item on the Frame
+	 * that is not the title or name is then placed on the current frame. The
+	 * item that was clicked on is placed on the frame it was linked to and the
+	 * link is switched to the item from the child frame. If the given Item has
+	 * no link, or no item is found then this is a no-op.
+	 * 
+	 * @param current
+	 *            The Item that links to the Frame that the Item will be loaded
+	 *            from.
+	 */
+	public static void SwapItemWithItemOnChildFrame(Item current) {
+		Item item = getFirstBodyItemOnChildFrame(current);
+		// if no item was found
+		if (item == null) {
+			return;
+		}
+
+		// swap the items parents
+		Frame parentFrame = current.getParent();
+		Frame childFrame = item.getParent();
+		current.setParent(childFrame);
+		item.setParent(parentFrame);
+
+		// swap the items on the frames
+		parentFrame.removeItem(current);
+		childFrame.removeItem(item);
+		parentFrame.addItem(item);
+		childFrame.addItem(current);
+
+		// swap the items links
+		item.setAction(current.getAction());
+		item.setLink(childFrame.getFrameName());
+		current.setLink(parentFrame.getFrameName());
+		// current.setLink(null);
+		current.setAction(null);
+
+		FrameGraphics.Repaint();
+	}
+
+	private static Item getFirstBodyItemOnChildFrame(Item current) {
+		// the item must link to a frame
+		if (current.getLink() == null) {
+			FrameGraphics
+					.DisplayMessage("Cannot get item from child - this item has no link");
+			return null;
+		}
+
+		Frame child = FrameIO.LoadFrame(current.getAbsoluteLink());
+
+		// if the frame could not be loaded
+		if (child == null) {
+			FrameGraphics.ErrorMessage("Could not load child frame.");
+			return null;
+		}
+
+		// find the first non-title and non-name item
+		List<Item> body = child.getItems();
+		Item item = null;
+
+		for (Item i : body)
+			if (i != child.getTitle() && i != child.getName()
+					&& !i.isAnnotation()) {
+				item = i;
+				break;
+			}
+
+		// if no item was found
+		if (item == null) {
+			FrameGraphics.DisplayMessage("No item found to copy");
+			return null;
+		}
+
+		return item;
+	}
+}
Index: /trunk/src/org/expeditee/actions/NavigationActions.java
===================================================================
--- /trunk/src/org/expeditee/actions/NavigationActions.java	(revision 6)
+++ /trunk/src/org/expeditee/actions/NavigationActions.java	(revision 6)
@@ -0,0 +1,239 @@
+package org.expeditee.actions;
+
+import java.util.List;
+
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.items.Item;
+
+/**
+ * Provides the Navigation related KMS action procedures
+ * 
+ * @author jdm18
+ * 
+ */
+public class NavigationActions {
+
+	public static void setLastNavigationItem(Item i) {
+		_LastItemUsed = i;
+		if (i.getParent() != null) {
+			_Parent = i.getParent().getFrameName();
+		}
+	}
+
+	private static void Default() {
+		System.out.println("Not Implemented");
+	}
+
+	/**
+	 * Performs a back operation from the current Frame. If the back-stack is
+	 * empty, then nothing happens.
+	 */
+	public static void Back() {
+		DisplayIO.Back();
+	}
+
+	public static void ClearBack(Item i) {
+		Default();
+	}
+
+	public static void DeleteBack() {
+		Default();
+	}
+
+	public static void GetBack(String topFrame, int x, int y) {
+		Default();
+	}
+
+	public static void GetBackList() {
+		Default();
+	}
+
+	/**
+	 * Displays the user's home frame
+	 */
+	public static void GotoHome() {
+		FrameUtils.DisplayHomeFrame();
+	}
+
+	/**
+	 * Displays the user's profile frame (if there is one)
+	 */
+	public static void GotoProfile() {
+		FrameUtils.DisplayProfileFrame();
+	}
+
+	/**
+	 * Loads the Frame with the given FrameName and displays it
+	 * 
+	 * @param frameName
+	 *            The name of the Frame to load
+	 */
+	public static void Goto(String frameName) {
+		FrameUtils.DisplayFrame(frameName);
+	}
+
+	/**
+	 * Loads the Frame in the current frameset with the given number and
+	 * displays it
+	 * 
+	 * @param value
+	 *            The number of the Frame to load
+	 */
+	public static void Goto(Integer value) {
+		FrameUtils.DisplayFrame(value.toString());
+	}
+
+	/**
+	 * Loads the Frame linked to by the Item that has this action, if there is
+	 * one
+	 * 
+	 * @param source
+	 *            The Item that has a link to the Frame to load and display
+	 */
+	public static void GotoLink(Item source) {
+		FrameUtils.DisplayFrame(source.getLink());
+	}
+
+	/**
+	 * Turns TwinFrames off if it is on, otherwise does nothing
+	 */
+	public static void Large() {
+		if (DisplayIO.isTwinFramesOn())
+			DisplayIO.ToggleTwinFrames();
+	}
+
+	/**
+	 * Navigates to the Frame with the next highest frame number in the current
+	 * frameset. If the current frame is the last frame in the frameset, nothing
+	 * happens.
+	 */
+	public static void NextFrame() {
+		Frame next = FrameIO.LoadNext();
+		FrameUtils.DisplayFrame(next, true);
+	}
+	
+	/**
+	 * Navigates to the last frame in the frameset.
+	 *
+	 */
+	public static void LastFrame() {
+		Frame last = FrameIO.LoadLast();
+		FrameUtils.DisplayFrame(last, true);
+	}
+	
+
+	public static void Next() {
+		NextFrame();
+	}
+	
+	public static void Previous() {
+		PreviousFrame();
+	}
+	
+	public static void PreviousFrame() {
+		Frame prev = FrameIO.LoadPrevious();
+		FrameUtils.DisplayFrame(prev, true);
+	}
+
+	private static String _Parent = null;
+
+	private static Item _LastItemUsed = null;
+
+	public static void NextChild(Frame source) {
+		String back = DisplayIO.peekFromBack(); // ByRob: "peekFromBack" is not
+		// an informative term, better to refer to back up stack
+		// if there is no parent frame (i.e. the user is on the home frame) //
+		// ByRob: this should say "for example, the user ...."
+		if (back == null) { // No frame was on the Backup stack
+			FrameGraphics.DisplayMessage("No Parent Frame Found.");
+			_LastItemUsed = null; // ByRob: what is reason for setting
+			// this to null, who is going to use
+			// it????
+			_Parent = null; // ByRob: what is reason for setting this to
+			// null, who is going to use it
+			return;
+		}
+
+		/*
+		 * if(_Parent == null || !back.equals(_Parent.getFrameName())){ _Parent =
+		 * FrameIO.LoadFrame(back); _LastItemUsed = null; }
+		 */
+
+		if (_Parent == null || !back.equals(_Parent)) { // ByRob: what the heck
+			// is the code doing?
+			// What is it setting
+			// up???????
+			_Parent = back;
+			_LastItemUsed = null;
+		}
+
+		Frame parent = FrameIO.LoadFrame(_Parent); // at this point we have
+		// a parent frame (but
+		// does LoadFrame access
+		// backup stack?
+
+		// find the next item to visit // ByRob: We should NOT be trying to
+		// remember state!!! at time of Next, getNameOfCurrentFrame!!!!
+		// Actually, no. It could be situation that there is more than one item
+		// on frame linked to CurrentFr
+
+		List<Item> items = parent.getItems(); // getItems method gets us the
+		// FirstItem on the frame
+		int parentItemLinkedToSource = 0; // ByMike: Will be set to the
+		// index of the item on the parent which follows the item linked to the
+		// child frame we are currently viewing.
+
+		// ByMike: if 'Next' has been clicked previously get the index of the
+		// item FOLLOWING the ParentItem linked to the source
+		if (_LastItemUsed != null) {
+			parentItemLinkedToSource = items.indexOf(_LastItemUsed);
+		}
+
+		// ByMike: If the 'Next' child is being sought for the 1st time...
+
+		String sourceName = source.getFrameName().toLowerCase();
+
+		// ByMike: Find the first occurence of a ParentItem linked to the source
+		while (parentItemLinkedToSource < items.size()
+				&& (items.get(parentItemLinkedToSource).getLink() == null || !items
+						.get(parentItemLinkedToSource).getLink().toLowerCase()
+						.equals(sourceName))) {
+			parentItemLinkedToSource++; // ByRob: this increments to the next
+			// item
+		}
+
+		// Find the next ParentItem linked to the next frame to be displayed
+		for (int i = parentItemLinkedToSource + 1; i < items.size(); i++) {
+			if (items.get(i).isLinkValid()
+					&& !items.get(i).isAnnotation()
+					&& !items.get(i).getLink().toLowerCase().equals(
+							source.getFrameName().toLowerCase())) {
+				_LastItemUsed = items.get(i);
+				FrameUtils.DisplayFrame(_LastItemUsed.getLink(), false);
+				return;
+			} // ByRob: end of if
+
+		} // ByRob: End of For
+
+		FrameGraphics.DisplayMessage("No more child frames found.");
+	}
+
+	public static void OtherWindow() {
+		Default();
+	}
+
+	/**
+	 * Turns TwinFrames on if it is off, otherwise does nothing
+	 * 
+	 */
+	// ByRob: Rob doesn't like notion of turning TwinFrames on and off,
+	// better to say more explicitly/directly what the mode is!!!!
+	public static void Small() {
+		if (!DisplayIO.isTwinFramesOn())
+			DisplayIO.ToggleTwinFrames();
+	}
+}
Index: /trunk/src/org/expeditee/actions/Simple.java
===================================================================
--- /trunk/src/org/expeditee/actions/Simple.java	(revision 6)
+++ /trunk/src/org/expeditee/actions/Simple.java	(revision 6)
@@ -0,0 +1,1670 @@
+package org.expeditee.actions;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.event.InputEvent;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.gui.AttributeUtils;
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameMouseActions;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.io.Conversion;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Text;
+import org.expeditee.simple.BelowMinParametreCountException;
+import org.expeditee.simple.Context;
+import org.expeditee.simple.IncorrectParametreCountException;
+import org.expeditee.simple.IncorrectTypeException;
+import org.expeditee.simple.Pointers;
+import org.expeditee.simple.Primitives;
+import org.expeditee.simple.SBoolean;
+import org.expeditee.simple.SCharacter;
+import org.expeditee.simple.SInteger;
+import org.expeditee.simple.SPointer;
+import org.expeditee.simple.SPrimitive;
+import org.expeditee.simple.SReal;
+import org.expeditee.simple.SString;
+import org.expeditee.simple.SVariable;
+import org.expeditee.simple.UnitTestFailedException;
+import org.expeditee.stats.AgentStats;
+
+public class Simple {
+
+	private static final String DEFAULT_STRING = "$s.";
+
+	private static final String DEFAULT_BOOLEAN = "$b.";
+
+	private static final String DEFAULT_CHAR = "$c.";
+
+	private static final String DEFAULT_INTEGER = "$i.";
+
+	private static final String DEFAULT_REAL = "$r.";
+
+	private static final String DEFAULT_ITEM = "$ip.";
+
+	private static final String DEFAULT_FRAME = "$fp.";
+
+	private static final String EXIT_TEXT = "exitall";
+
+	private static enum Status {
+		Exit, OK, Break, Continue, Return, TrueIf, FalseIf;
+	};
+
+	private static final String BREAK_TEXT = "exitrepeat";
+
+	private static final String CONTINUE_TEXT = "nextrepeat";
+
+	private static final String RETURN_TEXT = "return";
+
+	private static final String LOOP_TEXT = "repeat";
+
+	private static final String TOKEN_SEPARATOR = " +";
+
+	private static final String RUN_FRAME_ACTION = "RunFrame";
+
+	/**
+	 * Keeps track of how many simple programs are running. Used to check if
+	 * simple should read in keyboard input. Or if the keyboard input should be
+	 * handled normally by Expeditee.
+	 */
+	private static int _programsRunning = 0;
+
+	public static void ProgramFinished() {
+		_programsRunning--;
+	}
+
+	private static LinkedList<Character> _KeyStrokes = new LinkedList<Character>();
+
+	public static void KeyStroke(char c) {
+		_KeyStrokes.add(c);
+	}
+
+	public static boolean isProgramRunning() {
+		return _programsRunning > 0;
+	}
+
+	public static void NewSimpleTest() {
+		Frame newSimpleTest = FrameIO.CreateFrame(DisplayIO.getCurrentFrame()
+				.getFramesetName(), "Test", null);
+		List<String> actions = new ArrayList<String>();
+		actions.add(RUN_FRAME_ACTION);
+		newSimpleTest.getTitle().setAction(actions);
+		FrameUtils.DisplayFrame(newSimpleTest, true);
+		FrameGraphics.DisplayMessage("New test created");
+	}
+
+	public static void NextTest() {
+		Frame next = DisplayIO.getCurrentFrame();
+		do {
+			next = FrameIO.LoadNext(next);
+		} while (next != null
+				&& (next.getTitle() == null || !RUN_FRAME_ACTION
+						.equalsIgnoreCase(next.getTitle().getFirstAction())));
+		FrameUtils.DisplayFrame(next, true);
+	}
+
+	public static void PreviousTest() {
+		Frame prev = DisplayIO.getCurrentFrame();
+		do {
+			prev = FrameIO.LoadPrevious(prev);
+		} while (prev != null
+				&& (prev.getTitle() == null || !RUN_FRAME_ACTION
+						.equalsIgnoreCase(prev.getTitle().getFirstAction())));
+
+		FrameUtils.DisplayFrame(prev, true);
+	}
+
+	public static void LastTest() {
+		Frame next = FrameIO.LoadLast();
+		Frame lastTest = null;
+		do {
+			// check if its a test frame
+			if (next != null
+					&& next.getTitle() != null
+					&& RUN_FRAME_ACTION.equalsIgnoreCase(next.getTitle()
+							.getFirstAction())) {
+				lastTest = next;
+				break;
+			}
+
+			next = FrameIO.LoadPrevious(next);
+		} while (next != null);
+
+		FrameUtils.DisplayFrame(lastTest, true);
+	}
+
+	public static void RunSimpleTests(String frameset) {
+		RunSimpleTests(frameset, false);
+	}
+
+	public static void RunSimpleTestsVerbose(String frameset) {
+		RunSimpleTests(frameset, true);
+	}
+
+	public static void RunSimpleTests(String frameset, boolean verbose) {
+		int testsPassed = 0;
+		int testsFailed = 0;
+
+		FrameIO.SaveFrame(DisplayIO.getCurrentFrame(), false);
+
+		FrameGraphics.DisplayMessage("Starting test suite: " + frameset,
+				Color.CYAN);
+
+		// Get the next number in the inf file for the frameset
+		int lastFrameNo = FrameIO.getLastNumber(frameset);
+
+		// Loop through all the valid frames in the frameset
+		for (int i = 1; i <= lastFrameNo; i++) {
+			String nextFrameName = frameset + i;
+			Frame nextFrame = FrameIO.LoadFrame(nextFrameName);
+			if (nextFrame != null) {
+				// Run the frames with the RunFrame action on the title
+				Text title = nextFrame.getTitle().copy();
+				List<String> actions = title.getAction();
+				if (actions != null && !title.isAnnotation()) {
+					if (actions.get(0).toLowerCase().equals("runframe")) {
+						try {
+							title.setLink(nextFrameName);
+							// TODO add the ability to run a setup frame
+							// which sets up variables to be used in all
+							// tests
+							AgentStats.reset();
+							_KeyStrokes.clear();
+							_programsRunning++;
+							RunFrameAndReportError(title, new Context());
+							_programsRunning--;
+							if (verbose)
+								FrameGraphics.DisplayMessage("Test passed: "
+										+ title.toString(), Item.GREEN);
+							testsPassed++;
+						} catch (Exception e) {
+							testsFailed++;
+							_programsRunning--;
+							// Print out the reason for failed tests
+							FrameGraphics.LinkedErrorMessage(e.getMessage());
+						}
+					}
+				}
+			}
+		}
+
+		// Report the number of test passed and failed
+		FrameGraphics.DisplayMessage("Total tests: "
+				+ (testsPassed + testsFailed), Color.CYAN);
+		if (testsPassed > 0)
+			FrameGraphics.DisplayMessage("Passed: " + testsPassed, Item.GREEN);
+		if (testsFailed > 0)
+			FrameGraphics.DisplayMessage("Failed: " + testsFailed, Color.RED);
+	}
+
+	public static void RunFrame(Item current) {
+		try {
+			FrameIO.SaveFrame(DisplayIO.getCurrentFrame(), true);
+
+			// an item without a link signals to run the current frame
+			if (current.getLink() == null) {
+				current = current.copy();
+				current.setLink(DisplayIO.getCurrentFrame().getFrameName());
+			}
+
+			_KeyStrokes.clear();
+
+			_programsRunning++;
+			Thread t = new Thread(current);
+			t.setPriority(Thread.MIN_PRIORITY);
+			t.start();
+			// _programsRunning--;
+		} catch (Exception e) {
+			// _programsRunning--;
+		}
+	}
+
+	private static void FlagError(Item item) {
+		item.setColor(Color.CYAN);
+		FrameUtils.DisplayFrame(item.getParent().getFrameName(), true);
+		FrameIO.SaveFrame(item.getParent());
+	}
+
+	/**
+	 * Runs a simple code begining on a frame linked to by the specified item
+	 * parametre.
+	 * 
+	 * @param current
+	 *            the item that is linked to the frame to be run.
+	 */
+	public static Status RunFrameAndReportError(Item current, Context context)
+			throws Exception {
+		// the item must link to a frame
+		if (current.getLink() == null) {
+			throw new Exception("Could not run unlinked item: "
+					+ current.toString());
+		}
+
+		Frame child = FrameIO.LoadFrame(current.getAbsoluteLink());
+		AgentStats.FrameExecuted();
+
+		// if the frame could not be loaded
+		if (child == null) {
+			throw new Exception("Could not load item link: "
+					+ current.toString());
+		}
+
+		// loop through non-title, non-name, non-annotation text items
+		List<Item> body = child.getItems();
+		Text item = null;
+		Status lastItemStatus = Status.OK;
+		for (Item i : body)
+			// Ignore items that arent statements
+			if (isStatement(i)) {
+				item = (Text) i;
+				AgentStats.ItemExecuted();
+				try {
+
+					lastItemStatus = RunItem(item, context, lastItemStatus);
+
+					if (lastItemStatus != Status.OK) {
+						if (lastItemStatus != Status.TrueIf
+								&& lastItemStatus != Status.FalseIf) {
+							return lastItemStatus;
+						}
+					}
+				} catch (ArrayIndexOutOfBoundsException e) {
+					FlagError(item);
+					throw new Exception("Too few parametres: "
+							+ item.toString());
+				} catch (NullPointerException e) {
+					FlagError(item);
+					throw new Exception("Null pointer exception: "
+							+ item.toString());
+				} catch (RuntimeException e) {
+					FlagError(item);
+					throw new Exception(e.getMessage() + " " + item.toString());
+				} catch (Exception e) {
+					throw new Exception(e.getMessage());
+				}
+			}
+
+		// if no item was found
+		if (item == null)
+			throw new Exception("No code to be executed: " + current.toString());
+
+		return Status.OK;
+	}
+
+	private static boolean isStatement(Item i) {
+		if (!(i instanceof Text))
+			return false;
+
+		Frame frame = i.getParent();
+
+		if (i == frame.getTitle() || i == frame.getName() || i.isAnnotation()) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * This method should be modified to parse strings correctly
+	 * 
+	 * @param statement
+	 * @return
+	 */
+	private static String[] parseStatement(String statement) {
+		return statement.split(TOKEN_SEPARATOR);
+	}
+
+	/**
+	 * Runs a SIMPLE procedure statement.
+	 * 
+	 * @param tokens
+	 *            the parsed Call statement.
+	 * @param code
+	 *            the expeditee item containing the procedure call and the link
+	 *            to the frame with the procedure code.
+	 * @param context
+	 *            the current context from which the procedure call is being
+	 *            made.
+	 * @throws Exception
+	 *             when errors occur in running the procedure.
+	 */
+	private static Status Call(String[] tokens, Text code, Context context)
+			throws Exception {
+		// Check that the call statement is linked
+		if (code.getLink() == null)
+			throw new Exception("Unlinked call statement: " + code.toString());
+
+		Frame procedure = FrameIO.LoadFrame(code.getAbsoluteLink());
+
+		// Add call to the start of the title if it doesnt exist
+		// This makes the call and signature tokens counts match
+		String procedureTitle = procedure.getTitle().getFirstLine();
+		if (!procedureTitle.toLowerCase().startsWith("call "))
+			procedureTitle = "call " + procedureTitle;
+
+		// Check that the calling statement matches the procedure
+		// signature
+		String[] procedureSignature = procedureTitle.split(TOKEN_SEPARATOR);
+
+		// Check for the right amount of parametres
+		if (procedureSignature.length < tokens.length)
+			throw new Exception("Call statement has too many parametres: "
+					+ code.toString());
+		else if (procedureSignature.length > tokens.length)
+			throw new Exception("Call statement has too few parametres: "
+					+ code.toString());
+		// else if (procedureSignature[1].equals(tokens[1]))
+		// throw new Exception("Call statement and procedure name dont match: "
+		// + code.toString());
+
+		// create the new context for the sub procedure
+		Context newContext = new Context();
+		// Check that the types/prefixes match
+		for (int i = 2; i < tokens.length; i++) {
+			// TODO allow for auto casting of primitives
+			if (tokens[i].substring(1, 2).equals(
+					procedureSignature[i].substring(1, 2))) {
+				// Add the variables to the new context
+				if (Primitives.isPrimitive(tokens[i])) {
+					try {
+						// try and get the value for the variable
+						SPrimitive p = context.getPrimitives().getVariable(
+								tokens[i]);
+						newContext.getPrimitives()
+								.add(procedureSignature[i], p);
+					} catch (Exception e) {
+						// If an exception occurs the variable doesnt
+						// exist in the current context
+						// So the variable must be added to both context
+						context.getPrimitives().add(tokens[i], new SString(""));
+						newContext.getPrimitives().add(procedureSignature[i],
+								new SString(""));
+					}
+				} else if (Pointers.isPointer(tokens[i])) {
+					try {
+						// try and get the value for the variable
+						SPointer p = context.getPointers().getVariable(
+								tokens[i]);
+						newContext.getPointers().add(procedureSignature[i], p);
+					} catch (Exception e) {
+						// If an exception occurs the variable doesnt
+						// exist in the current context
+						// So the variable must be added to both context
+						context.getPointers().setObject(tokens[i], null);
+						newContext.getPointers().setObject(
+								procedureSignature[i], null);
+					}
+				} else
+					throw new Exception("Unrecognised variable type: "
+							+ tokens[i] + " in " + code.toString());
+			} else
+				throw new IncorrectTypeException(procedureSignature[i], i);
+		}
+
+		// Follow the link and Run the code for the procedure
+		Status result = RunFrameAndReportError(code, newContext);
+		// If a return statement ends the procedure then we accept this as
+		// normal execution
+		switch (result) {
+		case Return:
+			result = Status.OK;
+			break;
+		case Break:
+			throw new Exception(BREAK_TEXT + " statement without matching "
+					+ LOOP_TEXT + " in " + code.toString());
+		case Continue:
+			throw new Exception("");
+		}
+
+		// Now copy the values from the procedure context into the
+		// current context
+		for (int i = 2; i < tokens.length; i++) {
+			try {
+				if (Primitives.isPrimitive(tokens[i])) {
+					// try and get the value for the variable
+					SVariable p = context.getPrimitives()
+							.getVariable(tokens[i]);
+					SVariable newP = newContext.getPrimitives().getVariable(
+							procedureSignature[i]);
+					p.setValue(newP);
+				} else {
+					// try and get the value for the variable
+					SVariable p = context.getPointers().getVariable(tokens[i]);
+					SVariable newP = newContext.getPointers().getVariable(
+							procedureSignature[i]);
+					p.setValue(newP);
+				}
+			} catch (Exception e) {
+				assert (false);
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * Runs a text item on a frame as a SIMPLE statement. The statement is
+	 * parsed and if it is a recognised SIMPLE keyword or procedure the code is
+	 * executed.
+	 * 
+	 * @param code
+	 *            the item containing the code to be executed.
+	 * @param context
+	 * @return
+	 * @throws Exception
+	 */
+	private static Status RunItem(Text code, Context context,
+			Status lastItemStatus) throws Exception {
+		String codeText = code.getTextNoList();
+		// Comments without links are a no-op
+		if (codeText.startsWith("#") || codeText.startsWith("//")) {
+			if (code.getLink() == null)
+				return Status.OK;
+			else
+				return RunFrameAndReportError(code, context);
+		}
+
+		// Now check for code statements
+		codeText = codeText.trim();
+		String[] tokens = parseStatement(codeText.toLowerCase());
+		// At present only set statements can have literals so they
+		// are the only statements that we have to worry about string
+		// literals
+		assert (tokens.length > 0);
+		if (tokens[0].equals("call") && tokens.length >= 2) {
+			return Call(tokens, code, context);
+			// Check if the user wants to display a message
+			// Check for set statements
+		} else if (tokens[0].startsWith("set")) {
+			if (tokens[0].equals("set")) {
+				try {
+					// Check if we are setting variable to variable
+					if (tokens[2].startsWith(SVariable.prefix)
+							&& tokens.length == 3) {
+						context.getPrimitives().set(tokens[1], tokens[2]);
+						return Status.OK;
+					}
+					// Otherwise we are setting a variable with a literal
+					else {
+						// check for strings enclosed in quotes
+						if (tokens[2].startsWith("\"")) {
+							if (tokens[tokens.length - 1].endsWith("\"")) {
+								context.getPrimitives().setValue(
+										tokens[1],
+										new SString(codeText.substring(codeText
+												.indexOf("\"") + 1, codeText
+												.length() - 1)));
+								return Status.OK;
+							} else
+								throw new Exception("Expected matching \" "
+										+ code.toString());
+							// set a literal
+						} else if (tokens.length == 3) {
+							context.getPrimitives().setValue(tokens[1],
+									tokens[2]);
+							return Status.OK;
+						}
+					}
+				} catch (Exception e) {
+					throw new RuntimeException(e.getMessage());
+				}
+			} else if (tokens[0].startsWith("setitem")) {
+				if (tokens[0].equals("setitemposition")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					// assertPrimitiveType(tokens[3], 3);
+					Item item = (Item) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					item.setPosition(context.getPrimitives().getVariable(
+							tokens[2]).integerValue().intValue(), context
+							.getPrimitives().getVariable(tokens[3])
+							.integerValue().intValue());
+				} else if (tokens[0].equals("setitemthickness")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Item item = (Item) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					item.setThickness(context.getPrimitives().getVariable(
+							tokens[2]).integerValue().intValue());
+				} else if (tokens[0].equals("setitemwidth")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Item item = (Item) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					item.setWidth(context.getPrimitives()
+							.getVariable(tokens[2]).integerValue().intValue());
+				} else if (tokens[0].equals("setitemsize")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Item item = (Item) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					item.setSize(context.getPrimitives().getVariable(tokens[2])
+							.integerValue().intValue());
+				} else if (tokens[0].equals("setitemlink")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Item item = (Item) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					item.setLink(context.getPrimitives().getVariable(tokens[2])
+							.stringValue());
+				} else if (tokens[0].equals("setitemfillcolor")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Item item = (Item) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					String stringColor = context.getPrimitives().getVariable(
+							tokens[2]).stringValue();
+					item.setBackgroundColor((Color) Conversion.Convert(
+							Color.class, stringColor));
+				} else if (tokens[0].equals("setitemcolor")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Item item = (Item) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					String stringColor = context.getPrimitives().getVariable(
+							tokens[2]).stringValue();
+					item.setColor((Color) Conversion.Convert(Color.class,
+							stringColor));
+				} else if (tokens[0].equals("setitemtext")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					String newText = context.getPrimitives().getVariable(
+							tokens[2]).stringValue();
+					Text textItem = (Text) context.getPointers().getVariable(
+							tokens[1]).getValue();
+					textItem.setText(newText);
+				} else
+					throw new Exception("Unsupported setItem command: "
+							+ code.toString());
+				return Status.OK;
+			} else if (tokens[0].equals("setstrchar")) {
+				assertExactParametreCount(tokens, 3);
+				StringBuffer s = new StringBuffer(context.getPrimitives()
+						.getStringValue(tokens[1]));
+				int pos = (int) context.getPrimitives().getIntegerValue(
+						tokens[2]);
+				char newChar = context.getPrimitives().getCharacterValue(
+						tokens[3]);
+				while (pos > s.length()) {
+					s.append(newChar);
+				}
+				s.setCharAt(pos - 1, newChar);
+
+				context.getPrimitives().setValue(tokens[1],
+						new SString(s.toString()));
+				return Status.OK;
+			} else if (tokens[0].equals("setcharinitem")) {
+				assertExactParametreCount(tokens, 4);
+				assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+
+				int row = (int) context.getPrimitives().getIntegerValue(
+						tokens[3]) - 1;
+				int col = (int) context.getPrimitives().getIntegerValue(
+						tokens[4]) - 1;
+				char newChar = context.getPrimitives().getCharacterValue(
+						tokens[2]);
+				Text item = (Text) context.getPointers().getVariable(tokens[1])
+						.getValue();
+				List<String> itemText = item.getText();
+
+				while (row >= itemText.size()) {
+					StringBuffer sb = new StringBuffer();
+					for (int i = 0; i <= col; i++)
+						sb.append(newChar);
+					itemText.add(sb.toString());
+				}
+				StringBuffer sb = new StringBuffer(itemText.get(row));
+				while (sb.length() <= col)
+					sb.append(newChar);
+				sb.setCharAt(col, newChar);
+
+				// set the modified string
+				itemText.set(row, sb.toString());
+				item.setTextList(itemText);
+
+				return Status.OK;
+			}
+		} else if (tokens[0].startsWith("assert")) {
+			if (tokens[0].equals("asserttrue")) {
+				assertExactParametreCount(tokens, 1);
+				if (!context.getPrimitives().getBooleanValue(tokens[1]))
+					throw new UnitTestFailedException("true", "false");
+				return Status.OK;
+			} else if (tokens[0].equals("assertfalse")) {
+				assertExactParametreCount(tokens, 1);
+				if (context.getPrimitives().getBooleanValue(tokens[1]))
+					throw new UnitTestFailedException("false", "true");
+				return Status.OK;
+			} else if (tokens[0].equals("assertnull")) {
+				assertExactParametreCount(tokens, 1);
+				Object value = context.getPrimitives().getVariable(tokens[1])
+						.getValue();
+				if (value != null)
+					throw new UnitTestFailedException("null", value.toString());
+				return Status.OK;
+			} else if (tokens[0].equals("assertequals")) {
+				assertExactParametreCount(tokens, 2);
+				if (!context.getPrimitives().equalValues(tokens[1], tokens[2]))
+					throw new UnitTestFailedException(context.getPrimitives()
+							.getStringValue(tokens[1]), context.getPrimitives()
+							.getStringValue(tokens[2]));
+				return Status.OK;
+			} else if (tokens[0].equals("assertdefined")) {
+				assertExactParametreCount(tokens, 1);
+				if (!context.isDefined(tokens[1])) {
+					throw new UnitTestFailedException(tokens[1] + " exists",
+							"not defined");
+				}
+				return Status.OK;
+			}
+		} else if (tokens[0].startsWith("goto")) {
+			String frameNameVar = DEFAULT_STRING;
+			if (tokens.length > 1) {
+				assertExactParametreCount(tokens, 1);
+				frameNameVar = tokens[1];
+			}
+			String frameName = context.getPrimitives().getStringValue(
+					frameNameVar);
+			NavigationActions.Goto(frameName);
+			return Status.OK;
+		} else if (tokens[0].startsWith("get")) {
+			if (tokens[0].startsWith("getframe")) {
+				if (tokens[0].equals("getframevalue")) {
+					assertMinParametreCount(tokens, 4);
+					assertVariableType(tokens[1], 1, SPointer.framePrefix);
+
+					// Get the attribute to be searched for on the target frame
+					Frame targetFrame = (Frame) context.getPointers()
+							.getVariable(tokens[1]).getValue();
+					String targetAttribute = context.getPrimitives()
+							.getStringValue(tokens[2]).toLowerCase()
+							+ ":";
+					Boolean found = false;
+					String value = "";
+					Text attributeItem = null;
+					Text valueItem = null;
+					// Begin the search
+					for (Text text : targetFrame.getBodyTextItems()) {
+						String s = text.getTextNoList().toLowerCase();
+						if (s.startsWith(targetAttribute)) {
+							attributeItem = text;
+							value = AttributeUtils.stripValue(s);
+							if (value.length() > 0) {
+								found = true;
+							}
+							break;
+						}
+					}
+					// Keep looking for a matching value nearby if we found an
+					// attribute without the value in the same item
+					if (!found && attributeItem != null) {
+						Point endPoint = attributeItem
+								.getEndParagraphPosition();
+
+						for (Text text : targetFrame.getBodyTextItems()) {
+							Point startPoint = text.getPosition();
+							if (Math.abs(startPoint.y - endPoint.y) < 10
+									&& Math.abs(startPoint.x - endPoint.x) < 20) {
+								found = true;
+								valueItem = text;
+								value = text.getTextNoList();
+								break;
+							}
+						}
+					}
+
+					// Set the values of the output parametres
+					context.getPrimitives().setValue(tokens[3],
+							new SString(value));
+					context.getPrimitives().setValue(tokens[4],
+							new SBoolean(found));
+					if (tokens.length > 5) {
+						context.getPointers().setObject(tokens[5],
+								attributeItem);
+						if (tokens.length > 6) {
+							context.getPointers().setObject(tokens[6],
+									valueItem);
+						}
+					}
+
+					return Status.OK;
+				} else if (tokens[0].startsWith("getframename")) {
+					String frameNameVar = DEFAULT_STRING;
+					String frameVar = DEFAULT_FRAME;
+
+					if (tokens.length > 1) {
+						assertExactParametreCount(tokens, 2);
+						assertVariableType(tokens[1], 1, SPointer.framePrefix);
+						frameNameVar = tokens[2];
+						frameVar = tokens[1];
+					}
+					Frame frame = (Frame) context.getPointers().getVariable(
+							frameVar).getValue();
+					context.getPrimitives().setValue(frameNameVar,
+							frame.getFrameName());
+					return Status.OK;
+				}
+			} else if (tokens[0].startsWith("getcurrent")) {
+				if (tokens[0].equals("getcurrentframe")) {
+					assertMinParametreCount(tokens, 1);
+					assertVariableType(tokens[1], 1, SPointer.framePrefix);
+
+					Frame currentFrame = DisplayIO.getCurrentFrame();
+					context.getPointers().setObject(tokens[1], currentFrame);
+
+					// check if the user is also after the frameName
+					if (tokens.length > 2) {
+						context.getPrimitives().setValue(tokens[2],
+								new SString(currentFrame.getFrameName()));
+					}
+					return Status.OK;
+				}
+			} else if (tokens[0].startsWith("getitem")) {
+				if (tokens[0].equals("getitemposition")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					// assertPrimitiveType(tokens[3], 3);
+					Point pos = ((Item) context.getPointers().getVariable(
+							tokens[1]).getValue()).getPosition();
+					Integer x = pos.x;
+					Integer y = pos.y;
+					context.getPrimitives()
+							.setValue(tokens[2], new SInteger(x));
+					context.getPrimitives()
+							.setValue(tokens[3], new SInteger(y));
+				} else if (tokens[0].equals("getitemthickness")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Float thickness = ((Item) context.getPointers()
+							.getVariable(tokens[1]).getValue()).getThickness();
+					context.getPrimitives().setValue(tokens[2],
+							new SReal(thickness));
+				} else if (tokens[0].equals("getitemwidth")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Integer width = ((Item) context.getPointers().getVariable(
+							tokens[1]).getValue()).getWidth();
+					context.getPrimitives().setValue(tokens[2],
+							new SInteger(width));
+				} else if (tokens[0].equals("getitemsize")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Integer size = ((Item) context.getPointers().getVariable(
+							tokens[1]).getValue()).getSize();
+					context.getPrimitives().setValue(tokens[2],
+							new SInteger(size));
+				} else if (tokens[0].equals("getitemlink")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					String link = ((Item) context.getPointers().getVariable(
+							tokens[1]).getValue()).getLink();
+					context.getPrimitives().setValue(tokens[2],
+							new SString(link));
+				} else if (tokens[0].equals("getitemfillcolor")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Color itemColor = ((Item) context.getPointers()
+							.getVariable(tokens[1]).getValue())
+							.getPaintBackgroundColor();
+					String color = itemColor.getRed() + " "
+							+ itemColor.getGreen() + " " + itemColor.getBlue();
+					context.getPrimitives().setValue(tokens[2],
+							new SString(color));
+				} else if (tokens[0].equals("getitemcolor")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					// assertPrimitiveType(tokens[2], 2);
+					Color itemColor = ((Item) context.getPointers()
+							.getVariable(tokens[1]).getValue()).getPaintColor();
+					String color = itemColor.getRed() + " "
+							+ itemColor.getGreen() + " " + itemColor.getBlue();
+					context.getPrimitives().setValue(tokens[2],
+							new SString(color));
+				} else if (tokens[0].equals("getitemtext")) {
+					assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+					Item item = ((Item) context.getPointers().getVariable(
+							tokens[1]).getValue());
+					context.getPrimitives().setValue(
+							tokens[2],
+							new SString((item instanceof Text) ? ((Text) item)
+									.getTextNoList() : ""));
+				} else
+					throw new Exception("Unsupported getItem command: "
+							+ code.toString());
+
+				return Status.OK;
+			} else if (tokens[0].equals("getstrchar")) {
+				assertExactParametreCount(tokens, 3);
+				String s = context.getPrimitives().getStringValue(tokens[1]);
+				int pos = (int) context.getPrimitives().getIntegerValue(
+						tokens[2]);
+
+				context.getPrimitives().setValue(tokens[3],
+						new SCharacter(s.charAt(pos - 1)));
+				return Status.OK;
+			} else if (tokens[0].equals("getstrlength")) {
+				assertExactParametreCount(tokens, 2);
+				String s = context.getPrimitives().getStringValue(tokens[1]);
+
+				context.getPrimitives().setValue(tokens[2],
+						new SInteger(s.length()));
+				return Status.OK;
+			} else if (tokens[0].equals("getelapsedtime")) {
+				assertExactParametreCount(tokens, 1);
+				context.getPrimitives().setValue(tokens[1],
+						new SInteger(AgentStats.getMilliSecondsElapsed()));
+				return Status.OK;
+			}
+		} else if (tokens[0].equals("or")) {
+			for (int i = 1; i < tokens.length - 1; i++) {
+				if (Primitives.isPrimitive(tokens[i])) {
+					if (context.getPrimitives().getBooleanValue(tokens[i])) {
+						context.getPrimitives().setValue(
+								tokens[tokens.length - 1], new SBoolean(true));
+						return Status.OK;
+					}
+				}
+			}
+			context.getPrimitives().setValue(tokens[tokens.length - 1],
+					new SBoolean(false));
+			return Status.OK;
+		} else if (tokens[0].equals("and")) {
+			for (int i = 1; i < tokens.length - 1; i++) {
+				if (Primitives.isPrimitive(tokens[i])) {
+					if (!context.getPrimitives().getBooleanValue(tokens[i])) {
+						context.getPrimitives().setValue(
+								tokens[tokens.length - 1], new SBoolean(false));
+						return Status.OK;
+					}
+				}
+			}
+			context.getPrimitives().setValue(tokens[tokens.length - 1],
+					new SBoolean(true));
+			return Status.OK;
+		} else if (tokens[0].equals("messagelnitem")
+				|| tokens[0].equals("messagelineitem")) {
+			String itemVar = DEFAULT_ITEM;
+
+			if (tokens.length > 1) {
+				assertExactParametreCount(tokens, 1);
+				itemVar = tokens[1];
+				assertVariableType(itemVar, 1, SPointer.itemPrefix);
+			}
+			Item message = (Item) context.getPointers().getVariable(itemVar)
+					.getValue();
+			try {
+				FrameGraphics.DisplayMessage(((Text) message).copy());
+			} catch (NullPointerException e) {
+				// Just ignore not text items!
+				FrameGraphics.DisplayMessage("null");
+			} catch (ClassCastException e) {
+				// Just ignore not text items!
+				FrameGraphics.DisplayMessage(message.toString());
+			} catch (Exception e) {
+				// Just ignore other errors
+			}
+			return Status.OK;
+		} else if (tokens[0].equals("messageln")
+				|| tokens[0].equals("messageline")
+				|| tokens[0].equals("errorln") || tokens[0].equals("errorline")) {
+			StringBuilder message = new StringBuilder();
+			if (tokens.length == 1) {
+				message.append(context.getPrimitives().getVariable(
+						DEFAULT_STRING).stringValue()
+						+ " ");
+			} else {
+				for (int i = 1; i < tokens.length; i++) {
+					if (Primitives.isPrimitive(tokens[i])) {
+						message.append(context.getPrimitives().getVariable(
+								tokens[i]).stringValue()
+								+ " ");
+					} else
+						throw new Exception("Illegal parametre: [" + tokens[i]
+								+ "] in line " + code.toString());
+				}
+			}
+
+			if (tokens[0].equals("errorln") || tokens[0].equals("errorline"))
+				FrameGraphics.ErrorMessage(message.toString());
+			else
+				FrameGraphics.DisplayMessageAlways(message.toString());
+			return Status.OK;
+		} else if (tokens[0].startsWith("else")) {
+			// if the if statement was false then run the else statement
+			if (lastItemStatus == Status.FalseIf) {
+				// check if it is a one line if statment
+				if (tokens.length > 1) {
+					// put together the one line statement
+					StringBuilder statement = new StringBuilder();
+					for (int i = 1; i < tokens.length; i++)
+						statement.append(tokens[i]).append(' ');
+					// create a copy of the code item to run
+					Text copiedCode = code.copy();
+					copiedCode.setText(statement.toString());
+					return RunItem(copiedCode, context, Status.OK);
+				} else {
+					return RunFrameAndReportError(code, context);
+				}
+			} else if (lastItemStatus == Status.TrueIf) {
+				return Status.OK;
+			}
+			throw new RuntimeException("Else without matching If statement");
+		} else if (tokens[0].startsWith("if")) {
+			Boolean result = null;
+			int parametres = 1;
+			String variable = DEFAULT_ITEM;
+			String ifStatement = tokens[0];
+			// Set the default variable
+			if (tokens.length == 1) {
+				if (ifStatement.equals("if") || ifStatement.equals("ifnot"))
+					variable = DEFAULT_STRING;
+				else if (ifStatement.equals("ifzero")
+						|| ifStatement.equals("ifnotzero"))
+					variable = DEFAULT_INTEGER;
+			} else {
+				variable = tokens[1];
+			}
+
+			if (ifStatement.equals("if")) {
+				result = context.getPrimitives().getBooleanValue(variable);
+			} else if (ifStatement.equals("ifnot")) {
+				result = !context.getPrimitives().getBooleanValue(variable);
+			} else if (ifStatement.equals("ifdefined")) {
+				result = context.isDefined(tokens[1]);
+			} else if (ifStatement.equals("ifnotdef")) {
+				result = !context.isDefined(tokens[1]);
+			} else if (ifStatement.equals("ifzero")) {
+				result = context.getPrimitives().getIntegerValue(variable) == 0;
+			} else if (ifStatement.equals("ifnotzero")) {
+				result = context.getPrimitives().getIntegerValue(variable) != 0;
+			} else if (tokens[0].equals("ifeq")) {
+				result = context.equalValues(tokens[1], tokens[2]);
+				parametres = 2;
+			} else if (tokens[0].equals("ifeqnocase")) {
+				result = context.getPrimitives().equalValuesNoCase(tokens[1],
+						tokens[2]);
+				parametres = 2;
+			} else if (tokens[0].equals("ifnoteqnocase")) {
+				result = !context.getPrimitives().equalValuesNoCase(tokens[1],
+						tokens[2]);
+				parametres = 2;
+			} else if (tokens[0].equals("ifnoteq")) {
+				result = !context.equalValues(tokens[1], tokens[2]);
+				parametres = 2;
+			} else if (tokens[0].equals("ifless")) {
+				result = context.getPrimitives().compareValues(tokens[1],
+						tokens[2]) < 0;
+				parametres = 2;
+			} else if (tokens[0].equals("ifgtr")) {
+				result = context.getPrimitives().compareValues(tokens[1],
+						tokens[2]) > 0;
+				parametres = 2;
+			} else if (tokens[0].equals("ifgeq")) {
+				result = context.getPrimitives().compareValues(tokens[1],
+						tokens[2]) >= 0;
+				parametres = 2;
+			} else if (tokens[0].equals("ifleq")) {
+				result = context.getPrimitives().compareValues(tokens[1],
+						tokens[2]) <= 0;
+				parametres = 2;
+			} else {
+				// assertVariableType(variable, 1, SPointer.itemPrefix);
+				if (ifStatement.equals("ifannotation")) {
+					result = ((Item) context.getPointers()
+							.getVariable(variable).getValue()).isAnnotation();
+				} else if (ifStatement.equals("iflinked")) {
+					result = ((Item) context.getPointers()
+							.getVariable(variable).getValue()).getLink() != null;
+				} else if (ifStatement.equals("ifactioned")) {
+					result = ((Item) context.getPointers()
+							.getVariable(variable).getValue()).getAction() != null;
+				} else if (ifStatement.equals("ifnotactioned")) {
+					result = ((Item) context.getPointers()
+							.getVariable(variable).getValue()).getAction() == null;
+				} else if (ifStatement.equals("ifbodytext")) {
+					Item i = (Item) context.getPointers().getVariable(variable)
+							.getValue();
+					result = i instanceof Text && !i.isFrameName()
+							&& !i.isFrameTitle();
+				} else if (ifStatement.equals("ifnotannotation")) {
+					result = !((Item) context.getPointers().getVariable(
+							variable).getValue()).isAnnotation();
+				} else if (ifStatement.equals("ifnotlinked")) {
+					result = ((Item) context.getPointers()
+							.getVariable(variable).getValue()).getLink() == null;
+				} else if (ifStatement.equals("ifnotbodytext")) {
+					Item i = (Item) context.getPointers().getVariable(variable)
+							.getValue();
+					result = !(i instanceof Text) || i.isFrameName()
+							|| i.isFrameTitle();
+				}
+			}
+
+			if (result == null)
+				throw new RuntimeException("Invalid If statement");
+			// Now check if we need to run the code
+			else if (result) {
+				Status status;
+				// check if it is a one line if statment
+				if (tokens.length > parametres + 1) {
+					// put together the one line statement
+					StringBuilder statement = new StringBuilder();
+					for (int i = parametres + 1; i < tokens.length; i++)
+						statement.append(tokens[i]).append(' ');
+					// create a copy of the code item to run
+					Text copiedCode = code.copy();
+					copiedCode.setText(statement.toString());
+					status = RunItem(copiedCode, context, Status.OK);
+				} else {
+					status = RunFrameAndReportError(code, context);
+				}
+				if (status == Status.OK) {
+					return Status.TrueIf;
+				} else {
+					return status;
+				}
+			}
+			return Status.FalseIf;
+
+		} // Look for variable length methods
+		else if (tokens[0].equals("attachitemtocursor")) {
+			String itemVar = DEFAULT_ITEM;
+
+			if (tokens.length > 1) {
+				assertExactParametreCount(tokens, 1);
+				assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+				itemVar = tokens[1];
+			}
+			Item item = (Item) context.getPointers().getVariable(itemVar)
+					.getValue();
+			FrameMouseActions.pickup(item);
+			return Status.OK;
+		} else if (tokens[0].equals("attachstrtocursor")) {
+			String stringVar = DEFAULT_STRING;
+
+			if (tokens.length > 1) {
+				assertExactParametreCount(tokens, 1);
+				stringVar = tokens[1];
+			}
+			String s = context.getPrimitives().getStringValue(stringVar);
+			Frame frame = DisplayIO.getCurrentFrame();
+			Text item = frame.createNewText(s);
+			FrameMouseActions.pickup(item);
+			// DisplayIO.repaint();
+			return Status.OK;
+		} else if (tokens[0].equals("additemtoframe")) {
+			String itemVar = DEFAULT_ITEM;
+			String frameVar = DEFAULT_FRAME;
+
+			if (tokens.length > 1) {
+				assertExactParametreCount(tokens, 2);
+				assertVariableType(tokens[1], 1, SPointer.framePrefix);
+				assertVariableType(tokens[2], 2, SPointer.itemPrefix);
+				itemVar = tokens[2];
+				frameVar = tokens[1];
+			}
+			Frame frame = (Frame) context.getPointers().getVariable(frameVar)
+					.getValue();
+			Item item = (Item) context.getPointers().getVariable(itemVar)
+					.getValue();
+			frame.addItem(item);
+
+			return Status.OK;
+		} else if (tokens[0].equals("connectdots")) {
+			assertMinParametreCount(tokens, 2);
+			assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+			assertVariableType(tokens[2], 2, SPointer.itemPrefix);
+			Dot dot1 = null;
+			Dot dot2 = null;
+			try {
+				dot1 = (Dot) context.getPointers().getVariable(tokens[1])
+						.getValue();
+			} catch (Exception e) {
+				throw new IncorrectTypeException("Dot", 1);
+			}
+			try {
+				dot2 = (Dot) context.getPointers().getVariable(tokens[2])
+						.getValue();
+			} catch (Exception e) {
+				throw new IncorrectTypeException("Dot", 2);
+			}
+			Frame frame = dot1.getParent();
+			frame.addItem(new Line(dot1, dot2, frame.getNextItemID()));
+			return Status.OK;
+		} else if (tokens[0].equals("createitem")) {
+			assertMinParametreCount(tokens, 4);
+			assertVariableType(tokens[1], 1, SPointer.framePrefix);
+			assertVariableType(tokens[4], 4, SPointer.itemPrefix);
+			Frame frame = (Frame) context.getPointers().getVariable(tokens[1])
+					.getValue();
+			Item newItem;
+			int x = (int) context.getPrimitives().getIntegerValue(tokens[2]);
+			int y = (int) context.getPrimitives().getIntegerValue(tokens[3]);
+			// check for the option text and action for the new item
+			if (tokens.length > 5) {
+				String newText = context.getPrimitives().getStringValue(
+						tokens[5]);
+				String newAction = null;
+				if (tokens.length > 6) {
+					newAction = context.getPrimitives().getStringValue(
+							tokens[6]);
+				}
+				newItem = frame.addText(x, y, newText, newAction);
+			} else
+				// create a point if the optional params are not provided
+				newItem = frame.addDot(x, y);
+			context.getPointers().setObject(tokens[4], newItem);
+
+			return Status.OK;
+		} else if (tokens[0].equals("createframe")) {
+
+			String framesetName = DEFAULT_STRING;
+			String frameVar = DEFAULT_FRAME;
+
+			if (tokens.length > 1) {
+				assertMinParametreCount(tokens, 2);
+				assertVariableType(tokens[2], 2, SPointer.framePrefix);
+				framesetName = tokens[1];
+				frameVar = tokens[2];
+			}
+
+			if (tokens.length > 3) {
+				context.createFrame(framesetName, frameVar, tokens[3]);
+			} else
+				context.createFrame(framesetName, frameVar, null);
+
+			return Status.OK;
+		} else if (tokens[0].equals("closeframe")) {
+			String frameVar = DEFAULT_FRAME;
+
+			if (tokens.length > 1) {
+				assertVariableType(tokens[1], 1, SPointer.framePrefix);
+				frameVar = tokens[1];
+			}
+
+			if (tokens.length > 2) {
+				// assertPrimitiveType(tokens[3], 3);
+				context.closeFrame(frameVar, tokens[3]);
+			} else
+				context.closeFrame(frameVar, null);
+
+			return Status.OK;
+		} else if (tokens[0].equals("readframe")
+				|| tokens[0].equals("openframe")) {
+
+			String frameName = DEFAULT_STRING;
+			String frameVar = DEFAULT_FRAME;
+
+			if (tokens.length > 1) {
+				assertMinParametreCount(tokens, 2);
+				assertVariableType(tokens[2], 2, SPointer.framePrefix);
+				frameName = tokens[1];
+				frameVar = tokens[2];
+				// assertPrimitiveType(frameName, 1);
+			}
+
+			if (tokens.length > 3) {
+				// assertPrimitiveType(tokens[3], 3);
+				context.readFrame(frameName, frameVar, tokens[3]);
+			} else
+				context.readFrame(frameName, frameVar, null);
+
+			return Status.OK;
+		} else if (tokens[0].equals("readkbdcond")) {
+
+			String nextCharVarName = DEFAULT_CHAR;
+			String wasCharVarName = DEFAULT_BOOLEAN;
+
+			if (tokens.length > 1) {
+				assertMinParametreCount(tokens, 2);
+				assertVariableType(tokens[2], 2, SPointer.framePrefix);
+				nextCharVarName = tokens[1];
+				wasCharVarName = tokens[2];
+			}
+
+			Character nextChar = _KeyStrokes.poll();
+			boolean hasChar = nextChar != null;
+			context.getPrimitives().setValue(wasCharVarName,
+					new SBoolean(hasChar));
+			if (hasChar)
+				context.getPrimitives().setValue(nextCharVarName,
+						new SCharacter(nextChar));
+
+			return Status.OK;
+		} else if (tokens[0].equals("openwritefile")) {
+			assertVariableType(tokens[1], 1, SString.prefix);
+			assertVariableType(tokens[2], 2, SPointer.filePrefix);
+
+			if (tokens.length > 3) {
+				assertVariableType(tokens[3], 3, SBoolean.prefix);
+				context.openWriteFile(tokens[1], tokens[2], tokens[3]);
+			} else
+				context.openWriteFile(tokens[1], tokens[2]);
+
+			return Status.OK;
+		} else if (tokens[0].equals("writefile")
+				|| tokens[0].equals("writelinefile")
+				|| tokens[0].equals("writelnfile")) {
+			assertVariableType(tokens[1], 1, SPointer.filePrefix);
+
+			StringBuilder textToWrite = new StringBuilder();
+			if (tokens.length == 1) {
+				textToWrite.append(context.getPrimitives().getVariable(
+						DEFAULT_STRING).stringValue()
+						+ " ");
+			} else {
+				for (int i = 2; i < tokens.length; i++) {
+					if (Primitives.isPrimitive(tokens[i])) {
+						textToWrite.append(context.getPrimitives().getVariable(
+								tokens[i]).stringValue());
+					} else
+						throw new Exception("Illegal parametre: " + tokens[i]
+								+ " in " + code.toString());
+				}
+			}
+
+			if (!tokens[0].equals("writefile"))
+				textToWrite.append('\n');
+			context.writeFile(tokens[1], textToWrite.toString());
+
+			return Status.OK;
+		} else if (tokens[0].equals("concatstr")) {
+			assertMinParametreCount(tokens, 3);
+			String resultVar = tokens[tokens.length - 1];
+
+			StringBuilder sb = new StringBuilder();
+			// loop through all the strings concatenating them
+			for (int i = 1; i < tokens.length - 1; i++) {
+				// assertPrimitiveType(tokens[i], i);
+				sb.append(context.getPrimitives().getStringValue(tokens[i]));
+			}
+			context.getPrimitives().setValue(resultVar,
+					new SString(sb.toString()));
+			return Status.OK;
+		} else if (tokens[0].equals("convstrlower")) {
+			assertExactParametreCount(tokens, 1);
+			// assertPrimitiveType(tokens[1], 1);
+			context.getPrimitives().setValue(
+					tokens[1],
+					new SString(context.getPrimitives().getStringValue(
+							tokens[1]).toLowerCase()));
+			return Status.OK;
+		} else if (tokens[0].equals("convstrupper")) {
+			assertExactParametreCount(tokens, 1);
+			// assertPrimitiveType(tokens[1], 1);
+			context.getPrimitives().setValue(
+					tokens[1],
+					new SString(context.getPrimitives().getStringValue(
+							tokens[1]).toUpperCase()));
+			return Status.OK;
+		} else if (tokens[0].equals("countcharsinstr")) {
+			assertExactParametreCount(tokens, 3);
+			String s = context.getPrimitives().getStringValue(tokens[1]);
+			String pattern = context.getPrimitives().getStringValue(tokens[2]);
+			int count = countCharsInString(s, pattern);
+			context.getPrimitives().setValue(tokens[3], new SInteger(count));
+			return Status.OK;
+		} else if (tokens[0].equals("countcharsinitem")) {
+			assertExactParametreCount(tokens, 3);
+			assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+			Item item = (Item) context.getPointers().getVariable(tokens[1])
+					.getValue();
+			String pattern = context.getPrimitives().getStringValue(tokens[2]);
+			int count = 0;
+			if (item instanceof Text)
+				count = countCharsInString(((Text) item).getTextNoList(),
+						pattern);
+			context.getPrimitives().setValue(tokens[3], new SInteger(count));
+			return Status.OK;
+		} else if (tokens[0].equals("parseframename")) {
+			assertExactParametreCount(tokens, 4);
+			String frameName = context.getPrimitives()
+					.getStringValue(tokens[1]);
+			String frameSet = "";
+			int frameNo = -1;
+			boolean success = true;
+			try {
+				frameSet = Conversion.getFrameset(frameName, false);
+				frameNo = Conversion.getFrameNumber(frameName);
+			} catch (Exception e) {
+				success = false;
+			}
+			// assertPrimitiveType(tokens[2], 2);
+			context.getPrimitives().setValue(tokens[2], new SBoolean(success));
+
+			// assertPrimitiveType(tokens[3], 3);
+			context.getPrimitives().setValue(tokens[3], new SString(frameSet));
+
+			// assertPrimitiveType(tokens[4], 4);
+			context.getPrimitives().setValue(tokens[4], new SInteger(frameNo));
+
+			return Status.OK;
+		} else if (tokens[0].equals("parsestr")) {
+			assertMinParametreCount(tokens, 2);
+			// assertPrimitiveType(tokens[1], 1);
+			// assertPrimitiveType(tokens[2], 2);
+
+			String s = context.getPrimitives().getStringValue(tokens[1]);
+
+			String separator = context.getPrimitives()
+					.getStringValue(tokens[2]);
+
+			String[] split = s.split(separator, tokens.length - 4);
+
+			if (tokens.length > 3) {
+				// assertPrimitiveType(tokens[3], 3);
+				int count = split.length;
+				// if the string is not blank and its got a remainder then
+				// decrease the count by 1 to account for the remainder
+				if (split.length != 0 && split.length > tokens.length - 5)
+					count--;
+
+				context.getPrimitives()
+						.setValue(tokens[3], new SInteger(count));
+
+				if (tokens.length > 4) {
+					// Set the remainder string
+					// assertPrimitiveType(tokens[4], 4);
+					if (split.length < tokens.length - 4)
+						context.getPrimitives().setValue(tokens[4],
+								new SString());
+					else
+						context.getPrimitives().setValue(tokens[4],
+								new SString(split[split.length - 1]));
+
+					// Set the strings for each of the vars
+					if (tokens.length > 5) {
+						for (int i = 5; i < tokens.length; i++) {
+							// assertPrimitiveType(tokens[i], i);
+							if (split.length < i - 4)
+								context.getPrimitives().setValue(tokens[i],
+										new SString());
+							else
+								context.getPrimitives().setValue(tokens[i],
+										new SString(split[i - 5]));
+						}
+					}
+				}
+			}
+			return Status.OK;
+		} else if (tokens[0].equals("stripstr")) {
+			assertExactParametreCount(tokens, 2);
+			String s = context.getPrimitives().getStringValue(tokens[1]);
+			String charsToStrip = context.getPrimitives().getStringValue(
+					tokens[2]);
+			for (int i = 0; i < charsToStrip.length(); i++)
+				s = s.replaceAll(charsToStrip.substring(i, i + 1), "");
+			context.getPrimitives().setValue(tokens[1], new SString(s));
+			return Status.OK;
+		} else if (tokens[0].equals("subststr")) {
+			assertExactParametreCount(tokens, 3);
+			String oldString = context.getPrimitives()
+					.getStringValue(tokens[2]);
+			String newString = context.getPrimitives()
+					.getStringValue(tokens[3]);
+			String result = context.getPrimitives().getStringValue(tokens[1]);
+			result = result.replaceAll(oldString, newString);
+			context.getPrimitives().setValue(tokens[1], new SString(result));
+			return Status.OK;
+		} else if (tokens[0].equals("substr")) {
+			assertExactParametreCount(tokens, 4);
+			int startPos = (int) context.getPrimitives().getIntegerValue(
+					tokens[2]) - 1;
+			int length = (int) context.getPrimitives().getIntegerValue(
+					tokens[3]);
+			String s = context.getPrimitives().getStringValue(tokens[1]);
+			String result;
+			if (startPos + length < s.length())
+				result = s.substring(startPos, startPos + length);
+			else
+				result = s.substring(startPos);
+			context.getPrimitives().setValue(tokens[4], new SString(result));
+			return Status.OK;
+		} else if (tokens[0].equals("pause")) {
+			String lengthVar = DEFAULT_REAL;
+
+			if (tokens.length > 1) {
+				assertExactParametreCount(tokens, 1);
+				lengthVar = tokens[1];
+			}
+
+			double time = context.getPrimitives().getDoubleValue(lengthVar);
+			for (int i = 0; i < time * 10; i++) {
+				Thread.yield();
+				Thread.sleep(100);
+			}
+			return Status.OK;
+		} else if (tokens[0].equals("glidecursorto")) {
+			assertMinParametreCount(tokens, 2);
+			int finalX = (int) context.getPrimitives().getIntegerValue(
+					tokens[1]);
+			int finalY = (int) context.getPrimitives().getIntegerValue(
+					tokens[2]);
+			int milliseconds = 1000;
+			if (tokens.length > 3)
+				milliseconds = (int) (context.getPrimitives().getDoubleValue(
+						tokens[3]) * 1000);
+
+			int initialX = DisplayIO.getMouseX();
+			int initialY = DisplayIO.getMouseY();
+
+			final int timeInterval = 40;
+
+			int deltaX = (int) (finalX - initialX);
+			int deltaY = (int) (finalY - initialY);
+
+			int intervals = milliseconds / timeInterval;
+			for (double i = 0; i < intervals; i++) {
+				int newX = initialX + (int) (deltaX * i / intervals);
+				int newY = initialY + (int) (deltaY * i / intervals);
+				Thread.yield();
+				Thread.sleep(timeInterval);
+				DisplayIO.setCursorPosition(newX, newY);
+				// DisplayIO.repaint();
+			}
+			// Thread.yield();
+			Thread.sleep(milliseconds % timeInterval);
+			DisplayIO.setCursorPosition(finalX, finalY);
+			return Status.OK;
+		}
+		// Now look for fixed parametre statements
+		else if (tokens.length == 1) {
+			if (tokens[0].equals(EXIT_TEXT)) {
+				return Status.Exit;
+			} else if (tokens[0].equals(LOOP_TEXT)) {
+				Status status = Status.OK;
+				// Keep looping until break or exit occurs
+				while (status == Status.OK || status == Status.Continue)
+					status = RunFrameAndReportError(code, context);
+				if (status == Status.Continue || status == Status.Break)
+					status = Status.OK;
+				return status;
+			} else if (tokens[0].equals(CONTINUE_TEXT)) {
+				return Status.Continue;
+			} else if (tokens[0].equals(BREAK_TEXT)) {
+				return Status.Break;
+			} else if (tokens[0].equals(RETURN_TEXT)) {
+				return Status.Return;
+			} else if (tokens[0].equals("pressleftbutton")) {
+				DisplayIO.pressMouse(InputEvent.BUTTON1_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("pressmiddlebutton")) {
+				DisplayIO.pressMouse(InputEvent.BUTTON2_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("pressrightbutton")) {
+				DisplayIO.pressMouse(InputEvent.BUTTON3_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("releaseleftbutton")) {
+				DisplayIO.releaseMouse(InputEvent.BUTTON1_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("releasemiddlebutton")) {
+				DisplayIO.releaseMouse(InputEvent.BUTTON2_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("releaserightbutton")) {
+				DisplayIO.releaseMouse(InputEvent.BUTTON3_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("clickleftbutton")) {
+				DisplayIO.clickMouse(InputEvent.BUTTON1_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("clickmiddlebutton")) {
+				DisplayIO.clickMouse(InputEvent.BUTTON2_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("clickrightbutton")) {
+				DisplayIO.clickMouse(InputEvent.BUTTON3_MASK);
+				return Status.OK;
+			} else if (tokens[0].equals("repaint")) {
+				DisplayIO.repaint();
+				return Status.OK;
+			}
+
+			if (tokens[0].equals("add")) {
+				context.getPrimitives().add(DEFAULT_INTEGER);
+			} else if (tokens[0].equals("subtract")) {
+				context.getPrimitives().subtract(DEFAULT_INTEGER);
+			} else
+				throw new RuntimeException("Invalid statement:");
+			return Status.OK;
+		} else if (tokens.length == 2) {
+			if (tokens[0].equals("closewritefile")) {
+				assertVariableType(tokens[1], 1, SPointer.filePrefix);
+				context.closeWriteFile(tokens[1]);
+
+				return Status.OK;
+			}
+
+			if (Primitives.isPrimitive(tokens[1])) {
+				if (tokens[0].equals("add")) {
+					context.getPrimitives().add(tokens[1]);
+				} else if (tokens[0].equals("subtract")) {
+					context.getPrimitives().subtract(tokens[1]);
+				} else
+					throw new RuntimeException("Invalid statement:");
+				return Status.OK;
+			}
+		} else if (tokens.length == 3) {
+			if (tokens[0].equals("foreach")) {
+				assertVariableType(tokens[1], 1, SPointer.itemPrefix);
+				assertVariableType(tokens[2], 2, SPointer.framePrefix);
+				Frame currFrame = (Frame) context.getPointers().getVariable(
+						tokens[2]).getValue();
+				// Create the ip variable
+				for (Item i : currFrame.getItems()) {
+					context.getPointers().setObject(tokens[1], i);
+					Status status = RunFrameAndReportError(code, context);
+					// check if we need to exit this loop because of
+					// statements in
+					// the code that was run
+					if (status == Status.Exit || status == Status.Return)
+						return status;
+					else if (status == Status.Break)
+						return Status.OK;
+				}
+				return Status.OK;
+			}
+
+			if (Primitives.isPrimitive(tokens[1])
+					&& Primitives.isPrimitive(tokens[2])) {
+				if (tokens[0].equals("movecursorto")) {
+					assertExactParametreCount(tokens, 2);
+					int x = (int) context.getPrimitives().getIntegerValue(
+							tokens[1]);
+					int y = (int) context.getPrimitives().getIntegerValue(
+							tokens[2]);
+					DisplayIO.setCursorPosition(x, y);
+					return Status.OK;
+				} else if (tokens[0].equals("not")) {
+					context.getPrimitives().not(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("exp")) {
+					context.getPrimitives().exp(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("log")) {
+					context.getPrimitives().log(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("log10")) {
+					context.getPrimitives().log10(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("sqrt")) {
+					context.getPrimitives().sqrt(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("add")) {
+					context.getPrimitives().add(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("subtract")) {
+					context.getPrimitives().subtract(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("multiply")) {
+					context.getPrimitives().multiply(tokens[1], tokens[2]);
+				} else if (tokens[0].equals("divide")) {
+					context.getPrimitives().divide(tokens[1], tokens[2]);
+				} else
+					throw new RuntimeException("Invalid statement:");
+				return Status.OK;
+			}
+		} else if (tokens.length == 4) {
+			if (Primitives.isPrimitive(tokens[1])
+					&& Primitives.isPrimitive(tokens[2])
+					&& Primitives.isPrimitive(tokens[3])) {
+				if (tokens[0].equals("add")) {
+					context.getPrimitives()
+							.add(tokens[1], tokens[2], tokens[3]);
+				} else if (tokens[0].equals("subtract")) {
+					context.getPrimitives().subtract(tokens[1], tokens[2],
+							tokens[3]);
+				} else if (tokens[0].equals("divide")) {
+					context.getPrimitives().divide(tokens[1], tokens[2],
+							tokens[3]);
+				} else if (tokens[0].equals("multiply")) {
+					context.getPrimitives().multiply(tokens[1], tokens[2],
+							tokens[3]);
+				} else if (tokens[0].equals("modulo")) {
+					context.getPrimitives().modulo(tokens[1], tokens[2],
+							tokens[3]);
+				} else if (tokens[0].equals("power")) {
+					context.getPrimitives().power(tokens[1], tokens[2],
+							tokens[3]);
+				} else
+					throw new Exception("Statement not found: " + tokens[0]
+							+ " in " + code.toString());
+				return Status.OK;
+			}
+		}
+		throw new RuntimeException("Unknown statement: ");
+	}
+
+	public static int countCharsInString(String s, String pattern) {
+		String newString = s;
+		int count = -1;
+		do {
+			count++;
+			s = newString;
+			newString = s.replaceFirst(pattern, "");
+		} while (s.length() != newString.length());
+
+		return count;
+	}
+
+	public static void assertVariableType(String varName, int no, String type)
+			throws Exception {
+		if (!varName.startsWith(type))
+			throw new IncorrectTypeException(type, no);
+	}
+
+	/*
+	 * public static void assertPrimitiveType(String varName, int no) throws
+	 * Exception { if (!Primitives.isPrimitive(varName)) throw new
+	 * IncorrectTypeException("primitive", no); }
+	 */
+
+	public static void assertMinParametreCount(String[] tokens,
+			int minParametres) throws Exception {
+		if (tokens.length - 1 < minParametres)
+			throw new BelowMinParametreCountException(minParametres);
+	}
+
+	public static void assertExactParametreCount(String[] tokens,
+			int parametreCount) throws Exception {
+		if (tokens.length - 1 != parametreCount)
+			throw new IncorrectParametreCountException(parametreCount);
+	}
+}
Index: /trunk/src/org/expeditee/agents/Agent.java
===================================================================
--- /trunk/src/org/expeditee/agents/Agent.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/Agent.java	(revision 6)
@@ -0,0 +1,65 @@
+package org.expeditee.agents;
+
+import org.expeditee.gui.Frame;
+
+/**
+ * The Interface that all Agents must implement. This interface allows the JAG
+ * classes\ to be loaded dynamically at run-time.
+ * 
+ * @author jdm18
+ * 
+ */
+public interface Agent extends Runnable {
+
+	/**
+	 * This method should always be called before calling process(). The Frame
+	 * passed in should be scanned for any paramters that should be set before
+	 * process() is called. If this method returns false then process() should
+	 * not be called.
+	 * 
+	 * @param toInit
+	 *            The Frame that has any parameters that need to be set before
+	 *            processing begins. Typically this is the Frame the Item that
+	 *            invoked this Agent is on.
+	 * @return True if the initialisation executed correctly, False if an error
+	 *         occured.
+	 */
+	public boolean initialise(Frame toInit);
+
+	/**
+	 * Sets the first Frame to process to the given Frame using any settings
+	 * previously set by a call to initialise(). Note that Tree based Agents
+	 * will use this Frame as the start Frame of the tree, and will then be
+	 * responsible for traversing the tree themselves.
+	 * 
+	 * @param toProcess
+	 *            The Frame apply this Agent to, or the root of the Tree for
+	 *            Tree-based Agents.
+	 */
+	public void setStartFrame(Frame start);
+
+	/**
+	 * Specifies whether this Agents produces a Frame of results to be shown to
+	 * the user after execution has completed, or if the program should return
+	 * to the Frame the user was on when they invoked this Agent.
+	 * 
+	 * @return True if this Agent produces a results Frame, false otherwise.
+	 */
+	public boolean hasResultFrame();
+
+	/**
+	 * Returns the results Frame produced by this Agent. This method is only
+	 * called if hasResultFrame() returns true. If hasResultFrame() returns
+	 * false, it is expected that this method returns null.
+	 * 
+	 * @return The result Frame produced by this Agent if there is one, or null
+	 *         otherwise.
+	 */
+	public Frame getResultFrame();
+
+	public abstract boolean isRunning();
+
+	public abstract void stop();
+
+	public abstract void interrupt();
+}
Index: /trunk/src/org/expeditee/agents/ClipboardTree.java
===================================================================
--- /trunk/src/org/expeditee/agents/ClipboardTree.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/ClipboardTree.java	(revision 6)
@@ -0,0 +1,20 @@
+package org.expeditee.agents;
+
+/**
+ * Writes the Tree of Frames, starting from the given Frame, to the Clipboard.
+ * Currently only Text output is supported, all other items are ignored.
+ * 
+ * @author jdm18
+ * 
+ */
+public class ClipboardTree extends WriteTree {
+
+	public ClipboardTree() {
+		super("clipboard");
+	}
+
+	public ClipboardTree(String format) {
+		super(format + " clipboard");
+	}
+
+}
Index: /trunk/src/org/expeditee/agents/ComputeTree.java
===================================================================
--- /trunk/src/org/expeditee/agents/ComputeTree.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/ComputeTree.java	(revision 6)
@@ -0,0 +1,103 @@
+package org.expeditee.agents;
+
+import org.expeditee.gui.AttributeUtils;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.items.Item;
+import org.expeditee.items.Text;
+
+public class ComputeTree extends DefaultAgent {
+
+	private Long startTime = 0L;
+
+	private int frameCount = 0;
+
+	private int itemCount = 0;
+
+	@Override
+	protected void finalise(Frame frame) {
+		Long total = System.currentTimeMillis() - startTime;
+		FrameGraphics.DisplayMessage("Time: " + total + "ms, frames: "
+				+ frameCount + ", items: " + itemCount);
+	}
+
+	@Override
+	protected Frame process(Frame frame) {
+		frameCount = 0;
+		itemCount = 0;
+
+		startTime = System.currentTimeMillis();
+
+		if (computeFrame(frame) == null) {
+			message("Nothing to compute!");
+		}
+
+		return null;
+	}
+
+	/**
+	 * Computes the value of a frame.
+	 * 
+	 * @param frame
+	 * @return the value of the frame or null if the frame does not have the
+	 *         compute tag or any values to compute;
+	 */
+	private Double computeFrame(Frame frame) {
+		// Search for @Compute frame tag
+		String computeTag = null;
+		for (Item i : frame.getItems()) {
+			if (i.isAnnotation()) {
+				String s = ((Text) i).getFirstLine().toLowerCase().trim();
+				if (s.startsWith("@compute:")) {
+					computeTag = s;
+					break;
+				}
+			}
+		}
+
+		// Check that the compute tag exists
+		if (computeTag == null)
+			return null;
+
+		// Iterate through all valid items on the frame performing the correct
+		// operation
+		char operator = computeTag.charAt(computeTag.length() - 1);
+		// double defaultValue = (operator == '*' ? 1.0 : 0.0);
+		Double result = null;
+
+		for (Item i : frame.getBodyTextItems()) {
+
+			Double value = null;
+			// Process the frame that each item is linked to
+			if (i.getLink() != null) {
+				value = computeFrame(FrameIO.LoadFrame(i.getAbsoluteLink()));
+				// Set the items text
+				if (value != null)
+					AttributeUtils.setSingleValue((Text) i, value.toString());
+			}
+
+			if (value == null) {
+				value = AttributeUtils.getDoubleValue(((Text) i)
+						.getTextNoList());
+			}
+
+			if (value != null) {
+				itemCount++;
+
+				// the first time we find an item init result
+				if (result == null)
+					result = (operator == '*' ? 1.0 : 0.0);
+
+				result = ((operator == '*') ? (result * value)
+						: (result + value));
+			}
+		}
+
+		if (result != null)
+			AttributeUtils.setSingleValue(frame.getTitle(), result.toString());
+		frameCount++;
+
+		return result;
+	}
+}
Index: /trunk/src/org/expeditee/agents/CopyTree.java
===================================================================
--- /trunk/src/org/expeditee/agents/CopyTree.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/CopyTree.java	(revision 6)
@@ -0,0 +1,131 @@
+package org.expeditee.agents;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.items.Item;
+
+public class CopyTree extends TreeProcessor {
+
+	private String _nameTo;
+
+	private String _nameFrom;
+
+	private String _framePath;
+
+	private int _lastNumber = -1;
+
+	private int _firstNumber = 1;
+
+	private HashMap<String, String> _nameMap = new HashMap<String, String>();
+
+	private LinkedList<String> _toReparse = new LinkedList<String>();
+
+	public CopyTree(String framesetTo) {
+		_nameTo = framesetTo.trim();
+	}
+
+	@Override
+	public boolean initialise(Frame init) {
+		_nameFrom = init.getFramesetName().toLowerCase();
+
+		// get the last used frame in the destination frameset
+		_lastNumber = FrameIO.getLastNumber(_nameTo);
+
+		if (_lastNumber < 0) {
+			// create the new frameset
+			Frame one = FrameIO.CreateFrameset(_nameTo, init.path);
+			_framePath = one.path;
+			_lastNumber = -1;
+			_firstNumber = 1;
+
+			// copy the original .0 frame
+			Frame zero = FrameIO.LoadFrame(init.getFramesetNameAdjusted() + "0");
+			processFrame(zero);
+		} else {
+			// get the frame path
+			Frame zero = FrameIO.LoadFrame(_nameTo + "0");
+			_framePath = zero.path;
+			_firstNumber = _lastNumber + 1;
+		}
+
+		return super.initialise(init);
+	}
+
+	@Override
+	protected void processFrame(Frame toProcess) {
+		// load a fresh copy of the frame that bypasses the cache
+		FrameIO.SuspendCache();
+
+		Frame fresh = FrameIO.LoadFrame(toProcess.getFrameName());
+		if (_nameMap.containsKey(fresh.getFrameName().toLowerCase())) {
+			fresh
+					.setFrameName(_nameMap.get(fresh.getFrameName()
+							.toLowerCase()));
+		} else {
+			fresh.setFrameset(_nameTo);
+			fresh.setFrameNumber(++_lastNumber);
+
+			_nameMap.put(toProcess.getFrameName().toLowerCase(), fresh
+					.getFrameName().toLowerCase());
+		}
+
+		boolean added = false;
+		for (Item i : fresh.getItems())
+			if (i.getLink() != null && !i.isAnnotation() && i.isLinkValid()) {
+				String link = i.getLink();
+				link = link.toLowerCase();
+				// link = link.replace(_nameFrom, _nameTo);
+				if (_nameMap.containsKey(link))
+					link = _nameMap.get(link);
+				else if (link.startsWith(_nameFrom)) {
+					_nameMap.put(link, _nameTo + (++_lastNumber));
+					link = _nameTo + _lastNumber;
+				}
+				i.setLink(link);
+			} else if (!added && i.getLink() != null && i.isAnnotation()
+					&& i.isLinkValid()) {
+				// annotation links need to be parsed at the end
+				if (i.getLink().toLowerCase().startsWith(_nameFrom)) {
+					_toReparse.add(fresh.getFrameName());
+					added = true;
+				}
+
+			}
+
+		fresh.path = _framePath;
+		FrameIO.SaveFrame(fresh);
+		FrameIO.ResumeCache();
+	}
+
+	@Override
+	protected void finalise(Frame frame) {
+		// reparse all frames that have annotation links that may need updating
+		for (String name : _toReparse) {
+			Frame toParse = FrameIO.LoadFrame(name);
+			boolean changed = false;
+			for (Item i : toParse.getItems()) {
+				if (i.getLink() != null && i.isAnnotation() && i.isLinkValid()) {
+					String link = i.getLink();
+					link = link.toLowerCase();
+					// link = link.replace(_nameFrom, _nameTo);
+					if (_nameMap.containsKey(link)) {
+						link = _nameMap.get(link);
+						i.setLink(link);
+						changed = true;
+					}
+				}
+			}
+
+			if (changed)
+				FrameIO.SaveFrame(toParse);
+		}
+
+		message("Tree successfully copied to " + _nameTo);
+		FrameUtils.DisplayFrame(Frame.GetFramesetNameAdjusted(_nameTo) + _firstNumber);
+	}
+
+}
Index: /trunk/src/org/expeditee/agents/DefaultAgent.java
===================================================================
--- /trunk/src/org/expeditee/agents/DefaultAgent.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/DefaultAgent.java	(revision 6)
@@ -0,0 +1,112 @@
+package org.expeditee.agents;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.stats.SessionStats;
+
+/**
+ * The framework for loading of agents accounts for two possible constructors.
+ * The first takes no parametres and is called when the agent is run without
+ * parametres. The second takes a single string parametre which is the
+ * constructor called when an agent is run with a parametre.
+ * 
+ * @author johnathon
+ * 
+ */
+public abstract class DefaultAgent implements Agent {
+
+	protected Frame _start = null;
+
+	protected Frame _end = null;
+
+	protected boolean _running = true;
+
+	protected boolean _stop = false;
+
+	/**
+	 * Performs any pre-processing of the starting frame, which may include
+	 * searching the Frame for tags that determine the Agent behaviour
+	 * 
+	 * @param start
+	 */
+	public boolean initialise(Frame init) {
+		return true;
+	}
+
+	public void setStartFrame(Frame start) {
+		_start = start;
+	}
+
+	public void run() {
+		// init is now called by Actions.java
+		/*
+		 * if(!initialise(_start)){ FrameGraphics.ErrorMessage("Error
+		 * initialising agent."); _running = false; return; }
+		 */
+
+		SessionStats.setEnabled(false);
+
+		_end = process(_start);
+
+		finalise(_start);
+
+		_running = false;
+
+		SessionStats.setEnabled(true);
+	}
+
+	public boolean hasResultFrame() {
+		return _end != null;
+	}
+
+	public Frame getResultFrame() {
+		return _end;
+	}
+
+	/**
+	 * Processes the given Frame, behaviour depends on individual
+	 * implementation. If this JAG displays any kind of completion Frame to the
+	 * user it should be returned from this method, otherwise null can be
+	 * returned.
+	 * 
+	 * @param frame
+	 *            the Frame to process
+	 * @return The completion Frame to show to the user, or null
+	 */
+	protected abstract Frame process(Frame frame);
+
+	/**
+	 * Performs any post-processing actions, such as displaying a completion
+	 * message to the user
+	 * 
+	 * @param frame
+	 *            The starting Frame this Agent was executed on
+	 */
+	protected abstract void finalise(Frame frame);
+
+	/**
+	 * Displays a message to the user
+	 * 
+	 * @param message
+	 *            The message to display to the user
+	 */
+	protected void message(String message) {
+		FrameGraphics.DisplayMessageAlways(message);
+	}
+
+	protected void overwriteMessage(String message) {
+		FrameGraphics.OverwriteMessage(message);
+	}
+
+	public boolean isRunning() {
+		return _running;
+	}
+
+	public void stop() {
+		_stop = true;
+	}
+
+	public void interrupt() {
+		stop();
+	}
+}
Index: /trunk/src/org/expeditee/agents/DisplayTree.java
===================================================================
--- /trunk/src/org/expeditee/agents/DisplayTree.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/DisplayTree.java	(revision 6)
@@ -0,0 +1,166 @@
+package org.expeditee.agents;
+
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.gui.TimeKeeper;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Text;
+
+/**
+ * Displays the Tree of Frames starting from the given frame. The delay between
+ * frames can be adjusted, either per-frame with the
+ * 
+ * @DisplayFramePause tag, or mid-stream with the
+ * @DisplayTreePause tag, both tags should be followed with the desired delay in
+ *                   ms. The default delay between Frames is 50ms.
+ * @author jdm18
+ * 
+ */
+public class DisplayTree extends TreeProcessor {
+
+	// delay between frames, in ms
+	private long _delay = 50;
+
+	// The shortest delay between frames
+	private static final long TIMER_RESOLUTION = 10;
+
+	private long _timeRemaining = 0;
+
+	private int _frameCount = 0;
+
+	private TimeKeeper _timer;
+
+	public DisplayTree(String delay) {
+		super();
+		try {
+			_delay = Long.parseLong(delay);
+		} catch (Exception e) {
+		}
+	}
+
+	public DisplayTree() {
+		super();
+	}
+
+	@Override
+	public boolean initialise(Frame start) {
+		_timer = new TimeKeeper();
+
+		// push current frame on to back-stack
+		DisplayIO.addToBack(start);
+
+		// MIKE: Doesnt think the line below is needed
+		// update the delays (if necessary)
+		// delay(start);
+
+		return true;
+	}
+
+	@Override
+	protected void processFrame(Frame toProcess) {
+		// ignore loops
+		if (toProcess != DisplayIO.getCurrentFrame())
+			FrameUtils.DisplayFrame(toProcess, false);
+
+		FrameUtils.ResponseTimer.restart();
+
+		// parse the frame for any pause settings
+		delay(toProcess);
+
+		_frameCount++;
+	}
+
+	/**
+	 * Parses through the given Frame to find any
+	 * 
+	 * @DisplayTreePause or
+	 * @DisplayFramePause tags, and updates the delay or pause as necessary
+	 * @param toSearch
+	 */
+	private void delay(Frame toSearch) {
+		// check for change in globaly delay time
+		Item delay = ItemUtils.FindTag(toSearch.getItems(),
+				"@DisplayTreePause:");
+		if (delay != null) {
+			try {
+				// attempt to read in the delay value
+				_delay = Long.parseLong(ItemUtils.StripTag(((Text) delay)
+						.getFirstLine(), "@DisplayTreePause:"));
+				message("DisplayTree delay changed to: " + _delay + "ms");
+			} catch (NumberFormatException nfe) {
+				message("Incorrect paramter for DisplayTreePause");
+			}
+
+		}
+
+		// check for change in delay for this frame only
+		delay = ItemUtils.FindTag(toSearch.getItems(), "@DisplayFramePause:");
+		if (delay != null) {
+			try {
+				// attempt to read in the delay value
+				long pause = Long.parseLong(ItemUtils.StripTag(((Text) delay)
+						.getFirstLine(), "@DisplayFramePause:"));
+				pause(pause);
+			} catch (NumberFormatException nfe) {
+				message("Incorrect paramter for DisplayFramePause");
+			}
+		} else
+			pause(_delay);
+	}
+
+	@Override
+	protected void finalise(Frame start) {
+		if (_frameCount == 0)
+			_frameCount++;
+		
+		long avg = _timer.getElapsedMillis() / _frameCount;
+		String stats = "Frames: " + _frameCount + ", Time: "
+				+ _timer.getElapsedStringMillis() + ", Average: " + avg;
+		String msg = "Tree successfully displayed. ";
+
+		// return the user to the Frame they started on
+		if (!_stop) {
+			DisplayIO.Back();
+		} else {
+			msg = "Agent halted by user. ";
+		}
+
+		message(msg + stats);
+	}
+
+	/**
+	 * Pauses the execution of this Agent for the given time period (in ms.)
+	 * This is used for the delay between displaying frames.
+	 * 
+	 * @param time
+	 */
+	private void pause(long time) {
+		try {
+			if (time < 0)
+				_timeRemaining = Long.MAX_VALUE;
+			else
+				_timeRemaining = time;
+
+			while (_timeRemaining > 0) {
+				Thread.yield();
+				Thread.sleep(TIMER_RESOLUTION);
+				_timeRemaining -= TIMER_RESOLUTION;
+			}
+		} catch (InterruptedException e) {
+		}
+	}
+
+	@Override
+	public void interrupt() {
+		_timeRemaining = 0;
+	}
+
+	@Override
+	public void stop() {
+		_timeRemaining = 0;
+		super.stop();
+	}
+
+}
Index: /trunk/src/org/expeditee/agents/DisplayTreeLeaves.java
===================================================================
--- /trunk/src/org/expeditee/agents/DisplayTreeLeaves.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/DisplayTreeLeaves.java	(revision 6)
@@ -0,0 +1,37 @@
+package org.expeditee.agents;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.items.Item;
+
+/**
+ * Displays the Tree of Frames starting from the given frame. The delay between
+ * frames can be adjusted, either per-frame with the
+ * 
+ * @DisplayFramePause tag, or mid-stream with the
+ * @DisplayTreePause tag, both tags should be followed with the desired delay in
+ *                   ms. The default delay between Frames is 50ms.
+ * @author jdm18
+ * 
+ */
+public class DisplayTreeLeaves extends DisplayTree {
+
+	public DisplayTreeLeaves(String delay) {
+		super(delay);
+	}
+
+	public DisplayTreeLeaves() {
+		super();
+	}
+
+	@Override
+	protected void processFrame(Frame toProcess) {
+		// call display trees process only if the frame doesnt have any linked
+		// items
+		for (Item item : toProcess.getItems()) {
+			if (item.getLink() != null && !item.isAnnotation())
+				return;
+		}
+
+		super.processFrame(toProcess);
+	}
+}
Index: /trunk/src/org/expeditee/agents/Format.java
===================================================================
--- /trunk/src/org/expeditee/agents/Format.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/Format.java	(revision 6)
@@ -0,0 +1,172 @@
+package org.expeditee.agents;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.items.Item;
+
+/**
+ * A simple formatting agent that aligns all non-annotation Items on the given
+ * Frame by the X and Y axis of the first Item on the Frame. This agent
+ * separates the items into columns, the actual alignment is done by
+ * FrameUtils.Align()
+ * 
+ * @author jdm18
+ * 
+ */
+public class Format extends DefaultAgent {
+	// whether the Items could be formatted successfully
+	private boolean _success = true;
+
+	// adjustmen from the default format spacing
+	private int _adjust = 0;
+
+	public Format() {
+	}
+
+	public Format(String param) {
+		try {
+			if (param.startsWith("+"))
+				param = param.substring(1);
+			_adjust = Integer.parseInt(param);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+	}
+
+	@Override
+	public Frame process(Frame start) {
+		ArrayList<Item> columnHeads = new ArrayList<Item>();
+
+		ArrayList<ArrayList<Item>> columns = new ArrayList<ArrayList<Item>>();
+		for (int j = 0; j < start.getItems().size(); j++) {
+			Item i = start.getItems().get(j);
+
+			if (j > 1 && start.isNormalTextItem(i)) {
+				int col = findColumn(columnHeads, i);
+				// if this is the head of a new column
+				if (col < 0) {
+					columnHeads.add(i);
+					columns.add(new ArrayList<Item>());
+					// otherwise the column for this item has already been
+					// found set the column to be the one we just added...
+					col = columns.size() - 1;
+				} else
+					columns.get(col).add(i);
+			}
+		}
+
+		// check for empty columns
+		int[] clear = new int[columnHeads.size()];
+		for (int i = 0; i < columns.size(); i++)
+			clear[i] = columns.get(i).size();
+
+		// remove empty columns
+		for (int i = (clear.length - 1); i >= 0; i--)
+			if (clear[i] == 0) {
+				columns.remove(i);
+				columnHeads.remove(i);
+			}
+
+		// if there are no columns to align, we are done
+		if (columns.size() == 0)
+			return null;
+
+		// sort the column heads by X position
+		Collections.sort(columnHeads, new Comparator<Item>() {
+			public int compare(Item o1, Item o2) {
+				return o1.getX() - o2.getX();
+			}
+
+		});
+
+		// sort lists by their X axis
+		Collections.sort(columns, new Comparator<ArrayList<Item>>() {
+			public int compare(ArrayList<Item> o1, ArrayList<Item> o2) {
+				if (o2.size() == 0)
+					return -10;
+
+				if (o1.size() == 0)
+					return 10;
+
+				Item i1 = o1.get(0);
+				Item i2 = o2.get(0);
+
+				return (i1.getX() - i2.getX());
+			}
+
+		});
+
+		int res = FrameUtils.Align(columns.get(0), true, _adjust);
+		_success = _success && (res >= 0);
+
+		for (int i = 0; i < columns.size() - 1; i++) {
+			List<Item> list = columns.get(i);
+
+			int maxX = 0;
+			int maxY = 0;
+			for (Item item : list) {
+				maxX = Math.max(maxX, item.getX() + item.getBoundsWidth());
+				maxY = Math.max(maxY, item.getY() + item.getBoundsHeight());
+			}
+
+			int xCheck = maxX;
+			for (Item item : columns.get(i + 1))
+				xCheck = Math.max(xCheck, maxX
+						+ /* item.getX() + */item.getBoundsWidth());
+
+			if (xCheck < FrameGraphics.getMaxSize().width) {
+				if (columnHeads.get(i + 1).getX() < maxX
+						&& columnHeads.get(i + 1).getY() < maxY)
+					columnHeads.get(i + 1).setX(maxX);
+
+				for (Item item : columns.get(i + 1))
+					if (item.getX() < maxX && item.getY() < maxY)
+						item.setX(maxX);
+			}
+
+			res = FrameUtils.Align(columns.get(i + 1), true, _adjust);
+			_success = _success && (res >= 0);
+		}
+
+		// align all the separate columns
+		/*
+		 * for(ArrayList<Item> l : columns){ int res = FrameUtils.Align(l,
+		 * true); _success = _success && (res >= 0); }
+		 */
+		start.setChanged(true);
+		FrameGraphics.Repaint();
+		return null;
+	}
+
+	/**
+	 * Finds which column contains the given Item, and returns the index to the
+	 * column in the start & end lists. Returns -1 if no column is found that
+	 * contains the given Item.
+	 * 
+	 * @param toFind
+	 *            The Item to determine the correct column for
+	 * @return The index of the column in the lists, or -1 if no column is found
+	 */
+	private int findColumn(ArrayList<Item> columnHeads, Item toFind) {
+		for (Item top : columnHeads)
+			if (FrameUtils.inSameColumn(top, toFind))
+				return columnHeads.indexOf(top);
+
+		return -1;
+	}
+
+	@Override
+	protected void finalise(Frame start) {
+		/*
+		 * if(_success) message("Formating complete.");
+		 */
+	}
+
+}
Index: /trunk/src/org/expeditee/agents/Sort.java
===================================================================
--- /trunk/src/org/expeditee/agents/Sort.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/Sort.java	(revision 6)
@@ -0,0 +1,60 @@
+package org.expeditee.agents;
+
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.items.Item;
+import org.expeditee.items.Text;
+
+public class Sort extends DefaultAgent {
+
+	/**
+	 * Sorts all the text Items on the given Frame alphabetically. The Items are
+	 * then rearranged to reflect this ordering.
+	 */
+	public Frame process(Frame start) {
+		ArrayList<Text> textItems = new ArrayList<Text>();
+
+		for (Item i : start.getItems())
+			if (i instanceof Text)
+				// do not sort title and framename
+				if (i.getID() > -1 && i != start.getTitle()
+						&& !i.isAnnotation()) {
+					textItems.add((Text) i);
+				}
+
+		// copy current positions of items
+		ArrayList<Point> positions = new ArrayList<Point>(textItems.size());
+
+		for (int i = 0; i < textItems.size(); i++)
+			positions.add(i, textItems.get(i).getPosition());
+
+		// Sort text items by their strings
+		Collections.sort(textItems, new Comparator<Text>() {
+			public int compare(Text a, Text b) {
+				return String.CASE_INSENSITIVE_ORDER.compare(a.getFirstLine(),
+						b.getFirstLine());
+			}
+		});
+
+		// update positions based on new order
+		for (int i = 0; i < positions.size(); i++)
+			textItems.get(i)
+					.setPosition(positions.get(i).x, positions.get(i).y);
+
+		// items will need to be resorted after this
+		start.setResort(true);
+		FrameGraphics.Repaint();
+
+		return null;
+	}
+
+	@Override
+	protected void finalise(Frame start) {
+		message("Sorting Complete.");
+	}
+}
Index: /trunk/src/org/expeditee/agents/SwitchyardTree.java
===================================================================
--- /trunk/src/org/expeditee/agents/SwitchyardTree.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/SwitchyardTree.java	(revision 6)
@@ -0,0 +1,64 @@
+package org.expeditee.agents;
+
+import java.awt.Color;
+import java.awt.Point;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.items.Text;
+
+public class SwitchyardTree extends DefaultAgent {
+
+	private Long startTime = 0L;
+
+	private int frameCount = 0;
+
+	private int itemCount = 0;
+
+	@Override
+	protected void finalise(Frame frame) {
+		Long total = System.currentTimeMillis() - startTime;
+		FrameGraphics.DisplayMessage("Time: " + total + "ms, frames: "
+				+ frameCount + ", items: " + itemCount);
+	}
+
+	@Override
+	protected Frame process(Frame frame) {
+		frameCount = 0;
+		itemCount = 0;
+		FrameGraphics.DisplayMessage("Running switchyard tree...");
+		startTime = System.currentTimeMillis();
+
+		for (Text textItem : frame.getBodyTextItems()) {
+			// Delete all non-annotations with more that one letter
+			if (textItem.getTextNoList().length() > 1)
+				frame.removeItem(textItem);
+			else {
+				// goto child frame of any linked 1 letter items
+				String link = textItem.getAbsoluteLink();
+				if (link != null) {
+					Frame childFrame = FrameIO.LoadFrame(link);
+					if (childFrame != null) {
+						Point lastItemEnd = textItem.getEndParagraphPosition();
+						for (Text childItem : childFrame.getBodyTextItems()) {
+							// look for red items
+							if (childItem.getColor().equals(Color.RED)) {
+								// make a copy and add to parent frame
+								Text itemCopy = childItem.copy();
+								// add to the right of parent item
+								lastItemEnd.translate(20, 0);
+								itemCopy.setPosition(lastItemEnd);
+								lastItemEnd = itemCopy
+										.getEndParagraphPosition();
+								frame.addItem(itemCopy);
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return null;
+	}
+}
Index: /trunk/src/org/expeditee/agents/TreeProcessor.java
===================================================================
--- /trunk/src/org/expeditee/agents/TreeProcessor.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/TreeProcessor.java	(revision 6)
@@ -0,0 +1,120 @@
+package org.expeditee.agents;
+
+import java.util.List;
+import java.util.Stack;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.items.Item;
+
+/**
+ * This class provides some generic methods that process a tree of Frames.
+ * Subclasses that only use text items can simply implement
+ * processTextItem(Text, int)
+ */
+public abstract class TreeProcessor extends DefaultAgent {
+
+	// the list of frames currently being processed
+	private Stack<FrameCounter> _frames = new Stack<FrameCounter>();
+
+	/**
+	 * Processes the Frame by calling processItem() for each unlinked item
+	 * encountered. Linked items call processStartLinkItem(), then load the
+	 * linked frame and process it recursively, upon returning
+	 * processEndLinkItem() is called.
+	 */
+	protected Frame process(Frame toProcess) {
+		if (_stop)
+			return null;
+
+		if (toProcess == null)
+			return null;
+
+		_frames.push(new FrameCounter(toProcess.getFrameName(), -1));
+
+		// process the entire tree of frames in depth-first order
+		while (_frames.size() > 0) {
+			if (_stop)
+				return null;
+
+			FrameCounter cur = _frames.pop();
+
+			Frame next = FrameIO.LoadFrame(cur.frame);
+			if (next == null) {
+				message("There is no " + cur.frame + " Frame to display");
+				return null;
+			}
+
+			if (cur.index < 0) {
+				if (next != null)
+					overwriteMessage("Processing: " + next.getFrameName());
+				processFrame(next);
+			}
+
+			// the items on the frame currently being processed.
+			List<Item> items = next.getItems();
+
+			// resume from the next item in the list
+			for (int i = cur.index + 1; i < items.size(); i++) {
+				Item item = items.get(i);
+
+				// ignore annotation and framenames
+				if (!item.isAnnotation() && item.getID() >= 0) {
+					if (item.getLink() != null) {
+						cur.index = i;
+						_frames.push(cur);
+
+						Frame linked = FrameIO.LoadFrame(item.getAbsoluteLink());
+
+						// if the linked frame was found, then display it next
+						if (linked != null) {
+							FrameCounter fc = new FrameCounter(linked
+									.getFrameName(), -1);
+							if (!_frames.contains(fc)) {
+								// remember what frame we are on before
+								// processing it
+								_frames.push(fc);
+
+								// processFrame(linked);
+
+								// process the loaded frame immediately
+								// (depth-first)
+								break;
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return null;
+	}
+
+	protected abstract void processFrame(Frame toProcess);
+
+	/**
+	 * Inner class used to keep track of what frames have been seen, as well as
+	 * what Item in the Frame the processing was up to. Only Frame names are
+	 * stored to keep memory usage down.
+	 */
+	private class FrameCounter {
+		public int index;
+
+		public String frame;
+
+		public FrameCounter(String f, int i) {
+			frame = f.toLowerCase();
+			index = i;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof FrameCounter)
+				return (((FrameCounter) o).frame.equals(frame));// && fc.index
+			// == index);
+
+			return false;
+		}
+	}
+
+}
Index: /trunk/src/org/expeditee/agents/WriteFrame.java
===================================================================
--- /trunk/src/org/expeditee/agents/WriteFrame.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/WriteFrame.java	(revision 6)
@@ -0,0 +1,14 @@
+package org.expeditee.agents;
+
+public class WriteFrame extends WriteTree {
+
+	public WriteFrame() {
+		super();
+		super.setFollowLinks(false);
+	}
+
+	public WriteFrame(String format) {
+		super(format);
+		super.setFollowLinks(false);
+	}
+}
Index: /trunk/src/org/expeditee/agents/WriteTree.java
===================================================================
--- /trunk/src/org/expeditee/agents/WriteTree.java	(revision 6)
+++ /trunk/src/org/expeditee/agents/WriteTree.java	(revision 6)
@@ -0,0 +1,200 @@
+package org.expeditee.agents;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import org.expeditee.actions.Actions;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.io.FrameWriter;
+import org.expeditee.io.Logger;
+import org.expeditee.io.TreeWriter;
+
+public class WriteTree extends DefaultAgent {
+
+	private String _format = "txt";
+
+	protected TreeWriter _treeWriter;
+
+	protected FrameWriter _frameWriter;
+
+	private boolean _clipboard = false;
+
+	// write tree and write frame are almost identical, so WriteFrame.java
+	// sets this to false
+	private boolean _followLinks = true;
+
+	private String _outFile = null;
+
+	private static final String IO_PACKAGE = Actions.ROOT_PACKAGE + "io.";
+
+	public WriteTree() {
+	}
+
+	public WriteTree(String params) {
+		String format = params.trim().toLowerCase();
+
+		if (format.equals("clipboard")) {
+			_clipboard = true;
+			return;
+		}
+
+		_format = format.trim();
+
+		int ind = params.indexOf(" ");
+		if (ind > 0) {
+			String param = params.substring(ind + 1);
+			_format = params.substring(0, ind).toLowerCase();
+
+			if (param.equals("clipboard"))
+				_clipboard = true;
+			else
+				_outFile = param;
+		}
+	}
+
+	public void setFollowLinks(boolean val) {
+		_followLinks = val;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public boolean initialise(Frame start) {
+		_outFile = start.getExportFileName();
+
+		try {
+			Class c = null;
+
+			try {
+				c = Class
+						.forName(IO_PACKAGE + _format.toUpperCase() + "Writer");
+			} catch (NoClassDefFoundError ex) {
+				c = Class.forName(IO_PACKAGE + _format + "Writer");
+			}
+
+			Constructor con = c.getConstructor();
+			Object o = con.newInstance();
+
+			// if o is not a tree or frame writer, then it is not a valid
+			// format
+
+			if (_followLinks) {
+				// check that o is a valid treewriter
+				if (!(o instanceof TreeWriter)) {
+					FrameGraphics.WarningMessage(_format.toUpperCase()
+							+ " format cannot be used to write trees.");
+					return false;
+				}
+
+				_treeWriter = (TreeWriter) o;
+
+				if (_clipboard)
+					_treeWriter.setOutputLocation("clipboard");
+				else if (_outFile != null)
+					_treeWriter.setOutputLocation(_outFile);
+			} else {
+				if (!(o instanceof FrameWriter) && !(o instanceof TreeWriter)) {
+					FrameGraphics.WarningMessage(_format.toUpperCase()
+							+ " format cannot be used to write frames.");
+					return false;
+				}
+
+				_frameWriter = (FrameWriter) o;
+
+				if (_clipboard)
+					_frameWriter.setOutputLocation("clipboard");
+				else if (_outFile != null)
+					_frameWriter.setOutputLocation(_outFile);
+			}
+
+		} catch (ClassNotFoundException e) {
+			// TODO Auto-generated catch block
+			// e.printStackTrace();
+			FrameGraphics
+					.WarningMessage("The agent does not exist or has incorrect parametres.");
+			return false;
+		} catch (InstantiationException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return false;
+		} catch (IllegalAccessException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return false;
+		} catch (SecurityException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return false;
+		} catch (NoSuchMethodException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return false;
+		} catch (IllegalArgumentException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return false;
+		} catch (InvocationTargetException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return false;
+		}
+
+		return true;
+	}
+
+	@Override
+	protected void finalise(Frame start) {
+	}
+
+	@Override
+	protected Frame process(Frame frame) {
+		String msg = "Failed.";
+		try {
+
+			if (_followLinks)
+				msg = _treeWriter.writeTree(frame);
+			else
+				msg = _frameWriter.writeFrame(frame);
+
+		} catch (IOException e) {
+			System.out.println("Caught");
+			Logger.Log(e);
+			FrameGraphics.ErrorMessage("Exception in WriteTree: "
+					+ e.getMessage());
+			e.printStackTrace();
+			super.stop();
+			return null;
+		}
+
+		if (_stop)
+			message("WriteTree halted by user.");
+		else
+			message(msg);
+
+		return null;
+	}
+
+	@Override
+	public void stop() {
+		super.stop();
+
+		if (_treeWriter != null)
+			_treeWriter.stop();
+
+		if (_frameWriter != null)
+			_frameWriter.stop();
+	}
+
+	@Override
+	public boolean isRunning() {
+		if (_treeWriter != null)
+			return _treeWriter.isRunning();
+
+		if (_frameWriter != null)
+			return _frameWriter.isRunning();
+
+		return false;
+	}
+
+}
Index: /trunk/src/org/expeditee/gui/AttributeUtils.java
===================================================================
--- /trunk/src/org/expeditee/gui/AttributeUtils.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/AttributeUtils.java	(revision 6)
@@ -0,0 +1,661 @@
+package org.expeditee.gui;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.io.Conversion;
+import org.expeditee.items.Item;
+import org.expeditee.items.Text;
+
+/**
+ * This class provides the methods to extract and set attributes of Items and
+ * Frames. These methods are called when a user merges a text item with
+ * <code>Attribute: Value</code> pairs.
+ * 
+ * @author jdm18
+ * 
+ */
+public class AttributeUtils {
+
+	private static final char SEPARATOR_CHAR = ':';
+
+	private static final String SEPARATOR_STRING = SEPARATOR_CHAR + " ";
+
+	// List of method names to ignore when searching for a match
+	private static List<Method> _GetMethods = null;
+
+	private static HashMap<String, Method> _SetMethods = null;
+
+	// List of attributes which are ignored when copying.
+	private static List<String> _Ignore = null;
+
+	// List of method names to show in extraced lists even when they return
+	// null
+	// (Null is often used to indicate the default value is used)
+	private static List<Method> _AllowNull = null;
+
+	// private static HashMap<String, String> _Abbreviations = null;
+
+	/**
+	 * Initialises the _Ignore and _AllowNull lists.
+	 */
+	private static void initLists() {
+
+		Class[] param = {};
+
+		try {
+			_Ignore = new LinkedList<String>();
+			_Ignore.add("date");
+			_Ignore.add("datecreated");
+			_Ignore.add("d");
+			_Ignore.add("link");
+			_Ignore.add("l");
+
+			_AllowNull = new LinkedList<Method>();
+			_AllowNull.add(Item.class.getMethod("getColor", param));
+			_AllowNull.add(Item.class.getMethod("getBackgroundColor", param));
+
+			_AllowNull.add(Frame.class.getMethod("getBackgroundColor", param));
+			_AllowNull.add(Frame.class.getMethod("getForegroundColor", param));
+
+			_GetMethods = new LinkedList<Method>();
+			_GetMethods.add(Item.class.getMethod("getDateCreated", param));
+			_GetMethods.add(Item.class.getMethod("getSize", param));
+			_GetMethods.add(Item.class.getMethod("getColor", param));
+			_GetMethods.add(Item.class.getMethod("getBackgroundColor", param));
+			_GetMethods.add(Item.class.getMethod("getAction", param));
+			_GetMethods.add(Item.class.getMethod("getLink", param));
+			_GetMethods.add(Item.class.getMethod("getFillColor", param));
+			_GetMethods.add(Item.class.getMethod("getFillPattern", param));
+
+			_GetMethods.add(Item.class.getMethod("getOwner", param));
+			_GetMethods.add(Item.class.getMethod("getLinkMark", param));
+			_GetMethods.add(Item.class.getMethod("getActionMark", param));
+
+			_GetMethods
+					.add(Item.class.getMethod("getActionCursorEnter", param));
+			_GetMethods
+					.add(Item.class.getMethod("getActionCursorLeave", param));
+			_GetMethods.add(Item.class.getMethod("getActionEnterFrame", param));
+			_GetMethods.add(Item.class.getMethod("getActionLeaveFrame", param));
+
+			_GetMethods.add(Item.class.getMethod("getTopShadowColor", param));
+			_GetMethods
+					.add(Item.class.getMethod("getBottomShadowColor", param));
+			_GetMethods.add(Item.class.getMethod("getArrow", param));
+
+			_GetMethods.add(Item.class.getMethod("getLinePattern", param));
+			_GetMethods.add(Item.class.getMethod("getLinkFrameset", param));
+			_GetMethods.add(Item.class.getMethod("getLinkTemplate", param));
+
+			_GetMethods.add(Text.class.getMethod("getFamily", param));
+			_GetMethods.add(Text.class.getMethod("getFontStyle", param));
+			_GetMethods.add(Text.class.getMethod("getJustification", param));
+			_GetMethods.add(Text.class.getMethod("getWidth", param));
+
+			_GetMethods.add(Frame.class.getMethod("getOwner", param));
+			_GetMethods.add(Frame.class.getMethod("getProtection", param));
+			_GetMethods.add(Frame.class.getMethod("getDateCreated", param));
+			_GetMethods.add(Frame.class.getMethod("getLastModifyUser", param));
+			_GetMethods.add(Frame.class.getMethod("getLastModifyDate", param));
+			_GetMethods.add(Frame.class.getMethod("getForegroundColor", param));
+			_GetMethods.add(Frame.class.getMethod("getBackgroundColor", param));
+
+			Class[] pString = { String.class };
+			Class[] pInt = { int.class };
+			Class[] pColor = { Color.class };
+			Class[] pBool = { boolean.class };
+			Class[] pArrow = { int.class, double.class };
+			Class[] pList = { List.class };
+			Class[] pIntArray = { int[].class };
+
+			_SetMethods = new HashMap<String, Method>();
+
+			_SetMethods.put("size", Item.class.getMethod("setSize", pInt));
+			_SetMethods.put("s", Item.class.getMethod("setSize", pInt));
+
+			_SetMethods.put("color", Item.class.getMethod("setColor", pColor));
+			_SetMethods.put("c", Item.class.getMethod("setColor", pColor));
+
+			_SetMethods.put("backgroundcolor", Item.class.getMethod(
+					"setBackgroundColor", pColor));
+			_SetMethods.put("bgc", Item.class.getMethod("setBackgroundColor",
+					pColor));
+
+			_SetMethods.put("action", Item.class.getMethod("setAction", pList));
+			_SetMethods.put("a", Item.class.getMethod("setAction", pList));
+
+			_SetMethods.put("link", Item.class.getMethod("setLink", pString));
+			_SetMethods.put("l", Item.class.getMethod("setLink", pString));
+
+			_SetMethods.put("fillcolor", Item.class.getMethod("setFillColor",
+					pColor));
+			_SetMethods.put("fc", Item.class.getMethod("setFillColor", pColor));
+
+			_SetMethods.put("fillpattern", Item.class.getMethod(
+					"setFillPattern", pString));
+
+			_SetMethods.put("owner", Item.class.getMethod("setOwner", pString));
+			_SetMethods.put("linkmark", Item.class.getMethod("setLinkMark",
+					pBool));
+			_SetMethods.put("actionmark", Item.class.getMethod("setActionMark",
+					pBool));
+
+			_SetMethods.put("actioncursorenter", Item.class.getMethod(
+					"setActionCursorEnter", pList));
+			_SetMethods.put("actioncursorleave", Item.class.getMethod(
+					"setActionCursorLeave", pList));
+			_SetMethods.put("actionenterframe", Item.class.getMethod(
+					"setActionEnterFrame", pList));
+			_SetMethods.put("actionleaveframe", Item.class.getMethod(
+					"setActionLeaveFrame", pList));
+
+			_SetMethods.put("topshadow", Item.class.getMethod(
+					"setTopShadowColor", pColor));
+			_SetMethods.put("bottomshadow", Item.class.getMethod(
+					"setBottomShadowColor", pColor));
+			_SetMethods.put("arrow", Item.class.getMethod("setArrow", pArrow));
+
+			_SetMethods.put("linepattern", Item.class.getMethod(
+					"setLinePattern", pIntArray));
+			_SetMethods.put("linkframeset", Item.class.getMethod(
+					"setLinkFrameset", pString));
+			_SetMethods.put("linktemplate", Item.class.getMethod(
+					"setLinkTemplate", pString));
+
+			_SetMethods.put("family", Text.class
+					.getMethod("setFamily", pString));
+			_SetMethods.put("face", Text.class.getMethod("setFontStyle", pString));
+			_SetMethods.put("fontstyle", Text.class.getMethod("setFontStyle", pString));
+			_SetMethods.put("justification", Text.class.getMethod(
+					"setJustification", pInt));
+			_SetMethods.put("width", Text.class.getMethod("setWidth", pInt));
+
+			_SetMethods.put("foregroundcolor", Frame.class.getMethod(
+					"setForegroundColor", pColor));
+			_SetMethods.put("fgc", Frame.class.getMethod("setForegroundColor",
+					pColor));
+			_SetMethods.put("backgroundcolor0", Frame.class.getMethod(
+					"setBackgroundColor", pColor));
+			_SetMethods.put("bgc0", Frame.class.getMethod("setBackgroundColor",
+					pColor));
+
+			/*
+			 * _Abbreviations = new HashMap<String, String>();
+			 * _Abbreviations.put("a", "Action"); _Abbreviations.put("l",
+			 * "Link"); _Abbreviations.put("c", "Color");
+			 * _Abbreviations.put("bgc", "BackgroundColor");
+			 * _Abbreviations.put("fgc", "ForegroundColor");
+			 * _Abbreviations.put("fc", "FillColor"); _Abbreviations.put("s",
+			 * "Size");
+			 */
+
+		} catch (SecurityException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchMethodException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Extracts a list of attributes from the given Object. Any method that
+	 * starts with <code>get</code>, takes no arguments and is not found in
+	 * the Ignore list will be run, All the attributes are then put into a Text
+	 * Item of the form <Name>:<Value> If the value returned by the get method
+	 * is null, then the attribute will not be included, unless the name of the
+	 * method is found in the AllowNull list.
+	 * 
+	 * @param toExtract
+	 *            The Object from which to extract the attributes
+	 * @return A Text Item containing the extracted Attributes.
+	 */
+	public static Text ExtractAttributes(Object toExtract) {
+		if (toExtract == null)
+			return null;
+
+		// ensure the lists are populated
+		if (_Ignore == null)
+			initLists();
+
+		// StringBuffer to store all the extracted Attribute:Value pairs
+		StringBuffer attributes = new StringBuffer();
+
+		// iterate through the list of methods
+		for (Method m : _GetMethods) {
+			// skip methods that are in the ignore list, do not start with
+			// 'get', or take parameters
+			if (m.getDeclaringClass() == toExtract.getClass()
+					|| m.getDeclaringClass() == toExtract.getClass()
+							.getSuperclass()) {
+				try {
+					Object o = m.invoke(toExtract, (Object[]) null);
+					if (o != null) {
+						// Class[] param = {o.getClass()};
+						// convert the returned object to a string
+						// try {
+						/*
+						 * //convert object types to primitives if(o.getClass() ==
+						 * Integer.class) param[0] = int.class; else
+						 * if(o.getClass() == LinkedList.class) param[0] =
+						 * List.class; else if(o.getClass() == Float.class)
+						 * param[0] = float.class; else if(o.getClass() ==
+						 * Double.class) param[0] = double.class;
+						 */
+						// the class must have both set and get methods for an
+						// attribute
+						// (if it has no set method this will throw an
+						// exception)
+						// toExtract.getClass().getMethod("set" +
+						// m.getName().substring(3), param);
+						// Convert the values returned into human readable
+						// values
+						if (o instanceof Integer) {
+							if (m.getName().endsWith("Justification")
+									&& convertJustificationToString((Integer) o) != null)
+								attributes
+										.append(m.getName().substring(3))
+										.append(SEPARATOR_STRING)
+										.append(
+												convertJustificationToString((Integer) o))
+										.append("\n");
+							// -1 indicates default value
+							else if (((Integer) o) > -1)
+								attributes.append(m.getName().substring(3))
+										.append(SEPARATOR_STRING).append(o)
+										.append("\n");
+						} else if (o instanceof Float) {
+							// -1 indicates default value
+							if (((Float) o) > -1)
+								attributes.append(m.getName().substring(3))
+										.append(SEPARATOR_STRING).append(o)
+										.append("\n");
+						} else if (o instanceof Double) {
+							// -1 indicates default value
+							if (((Double) o) > -1)
+								attributes.append(m.getName().substring(3))
+										.append(SEPARATOR_STRING).append(o)
+										.append("\n");
+						} else if (o instanceof Color) {
+							// converts the color to the KMS code
+							String s = Conversion.getKMSColorCode((Color) o);
+							if (s != null)
+								attributes.append(m.getName().substring(3))
+										.append(SEPARATOR_STRING).append(s)
+										.append("\n");
+						} else if (o instanceof Font) {
+							Font f = (Font) o;
+
+							String s = f.getName() + "-";
+							if (f.isPlain())
+								s += "Plain";
+
+							if (f.isBold())
+								s += "Bold";
+
+							if (f.isItalic())
+								s += "Italic";
+
+							s += "-" + f.getSize();
+
+							attributes.append(m.getName().substring(3)).append(
+									SEPARATOR_STRING).append(s).append("\n");
+						} else if (o instanceof Text) {
+							attributes.append(m.getName().substring(3)).append(
+									SEPARATOR_STRING).append(
+									((Text) o).getFirstLine()).append("\n");
+						} else if (o instanceof List) {
+							List list = (List) o;
+							for (Object ob : list)
+								attributes.append(m.getName().substring(3))
+										.append(SEPARATOR_STRING).append(ob)
+										.append("\n");
+						} else if (o instanceof Boolean) {
+							// true is the default for boolean values
+							if (!((Boolean) o).booleanValue())
+								attributes.append(m.getName().substring(3))
+										.append(SEPARATOR_STRING).append(o)
+										.append("\n");
+						} else
+							attributes.append(m.getName().substring(3)).append(
+									SEPARATOR_STRING).append(o).append("\n");
+
+						/*
+						 * } catch (SecurityException e) { e.printStackTrace(); }
+						 * /*catch (NoSuchMethodException e) {
+						 * e.printStackTrace(); }
+						 */
+
+						// methods that return null are only included if they
+						// are in the AllowNull list
+					} else if (_AllowNull.contains(m))
+						attributes.append(m.getName().substring(3)).append(
+								SEPARATOR_CHAR + "\n");
+				} catch (IllegalArgumentException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (IllegalAccessException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (InvocationTargetException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+
+		// if no attributes were extracted
+		if (attributes.length() <= 0)
+			return null;
+
+		while (attributes.charAt(attributes.length() - 1) == '\n')
+			attributes.delete(attributes.length() - 1, attributes.length());
+
+		// create the text Item
+		Text attribs = DisplayIO.getCurrentFrame().createNewText();
+		attribs.setText(attributes.toString());
+		attribs.setColor(null);
+		return attribs;
+	}
+
+	/**
+	 * Returns the String name of the justification value given. just should
+	 * correspond to one of the Item.JUSTIFICATION constants, if it does not
+	 * then null is returned.
+	 * 
+	 * @param just
+	 *            The justification level as defined by the Item.JUSTIFICATION
+	 *            constants.
+	 * @return The String name (i.e. "Center") of the justification given, or
+	 *         null if no match is found.
+	 */
+	private static String convertJustificationToString(int just) {
+		switch (just) {
+		case Item.JUSTIFICATION_CENTER:
+			return "Center";
+		case Item.JUSTIFICATION_FULL:
+			return "Full";
+		case Item.JUSTIFICATION_LEFT:
+			return "Left";
+		case Item.JUSTIFICATION_RIGHT:
+			return "Right";
+		default:
+			return null;
+		}
+	}
+
+	/**
+	 * Attempts to set the attribute in the given attribute: value pair. The
+	 * value string should be formatted as follows:
+	 * <code> Attribute: Value </code> Multiple values can be used if they are
+	 * separated by spaces
+	 * 
+	 * @param toSet
+	 *            The Item to set the attribute of
+	 * @param attribs
+	 *            The Text item that contains the list of attributes to set
+	 * @return True if the attribute(s) were sucessfully set, false otherwise
+	 */
+	public static boolean SetAttribute(Object toSet, Text attribs) {
+		// error checking
+		if (toSet == null || attribs == null)
+			return false;
+
+		if (_Ignore == null)
+			initLists();
+
+		// get the list of attribute: value pairs
+		List<String> values = attribs.getText();
+		// if no paris exist, we are done
+		if (values.size() == 0
+				|| (values.size() == 1 && values.get(0).length() == 0))
+			return false;
+
+		// loop through all attribute: value pairs
+		for (int i = 0; i < values.size(); i++) {
+			StringBuffer v = new StringBuffer(values.get(i));
+
+			// remove the annotation mark (if applicable)
+			if (v.indexOf("@") == 0)
+				v = v.deleteCharAt(0);
+
+			// check if the next string is another attribute to merge or a
+			// continuation
+			while (i < values.size() - 1) {
+				StringBuffer next = new StringBuffer(values.get(i + 1));
+
+				// if the next String has a colon, then it may be another
+				// attribute
+				if (next.indexOf("" + SEPARATOR_CHAR) >= 0) {
+					// if the attribute is the same as v, then it is a
+					// continuation
+					if (v.indexOf(stripAttribute(next.toString())) == 0) {
+						// strip the attribute from next
+						next = new StringBuffer(stripValue(next.toString()));
+						// if the attribute is not the same, then it may be a
+						// new method
+					} else {
+						// if this is indeed a method, then leave the while loop
+						// if(_SetMethods.containsKey(StripFromColon(next.toString()).toLowerCase())){
+						break;
+						// }
+					}
+				}
+
+				v.append("\n").append(next);
+				i++;
+			}
+
+			if (v.length() > 0
+					&& !SetAttribute(toSet, v.toString(), values.size() > 1)) {
+				// if no other attributes have been set
+				if (i == 0)
+					return false;
+				// otherwise, this is a list of attributes, so continue
+				else {
+					String stripped = stripAttribute(v.toString());
+					if (stripped == null) {
+						// This happens when there is an attribute at the start
+						// Then a bunch of plain text
+						return false;
+					} else if (_Ignore.contains(stripped)) {
+						return false;
+					} else if (!(_SetMethods.containsKey(stripped))) {
+						// Display an error message if its not in our list of
+						// attributes to ignore when copying
+						FrameGraphics.WarningMessage("Attribute: '"
+								+ stripAttribute(v.toString())
+								+ "' does not exist.");
+					} else {
+						String types = "";
+						for (Class c : _SetMethods.get(stripped)
+								.getParameterTypes())
+							types += c.getSimpleName() + " ";
+						FrameGraphics.WarningMessage("Wrong arguments for: '"
+								+ stripAttribute(v.toString()) + "' expecting "
+								+ types.trim() + " found '"
+								+ stripValue(v.toString()) + "'");
+					}
+				}
+			} else if (v.length() == 0)
+				return false;
+		}
+
+		return true;
+	}
+
+	private static boolean SetAttribute(Object toSet, String value,
+			boolean isAttributeList) {
+		// separate attribute and value from string
+		String attribute = stripAttribute(value);
+		value = stripValue(value);
+
+		// if there was no colon
+		if (value == null || attribute == null)
+			return false;
+
+		if (value.length() == 0)
+			value = "";
+
+		attribute = attribute.toLowerCase();
+
+		// Some properties are ignored when multiple attributes are being set on
+		// an item at the same time
+		if (isAttributeList && _Ignore.contains(attribute)) {
+			// System.out.println("Attribute ignored: " + attribute);
+			return true;
+		}
+
+		// separate multiple values if required
+		Method toRun = _SetMethods.get(attribute);
+
+		// if this is not the name of a method, it may be the name of an agent
+		if (toRun == null) {
+			// System.out.println("Attrib not found for: " + attribute);
+			return false;
+		}
+
+		// if there are duplicate methods with the same name
+		List<Method> possibles = new LinkedList<Method>();
+		if (toRun.getDeclaringClass() == toSet.getClass()
+				|| toRun.getDeclaringClass() == toSet.getClass()
+						.getSuperclass())
+			possibles.add(toRun);
+		int i = 0;
+		while (_SetMethods.containsKey(attribute + i)) {
+			if (_SetMethods.get(attribute + i).getDeclaringClass() == toSet
+					.getClass()
+					|| _SetMethods.get(attribute + i).getDeclaringClass() == toSet
+							.getClass().getSuperclass())
+				possibles.add(_SetMethods.get(attribute + i));
+			i++;
+		}
+
+		for (Method possible : possibles) {
+			Object current = null;
+			Object[] param = {};
+
+			// find the corresponding get method for this set method
+			for (Method m : _GetMethods) {
+				if (m.getDeclaringClass() == toSet.getClass()
+						&& m.getName()
+								.endsWith(possible.getName().substring(3))) {
+					try {
+						current = m.invoke(toSet, param);
+					} catch (IllegalArgumentException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					} catch (IllegalAccessException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					} catch (InvocationTargetException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+					break;
+				}
+
+			}
+
+			try {
+				Object[] params = Conversion.Convert(possible, value,
+						current);
+
+				try {
+					possible.invoke(toSet, params);
+					return true;
+				} catch (IllegalArgumentException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (IllegalAccessException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (InvocationTargetException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			} catch (NumberFormatException e) {
+
+			}
+		}
+
+		return false;
+
+	}
+
+	/**
+	 * Returns the part of the given string that is after a colon, or null if
+	 * the given String has no colon
+	 * 
+	 * @param toStrip
+	 *            The String to strip
+	 * @return The part of the String that is after a colon if there is one, or
+	 *         null if there is no colon
+	 */
+	public static String stripValue(String toStrip) {
+		int ind = toStrip.lastIndexOf(SEPARATOR_CHAR);
+		if (ind < 0)
+			return null;
+
+		toStrip = toStrip.substring(ind + 1);
+		// This line must be separate from above
+		// It removes spaces from the string returned by the above statement
+		toStrip = toStrip.trim();
+
+		return toStrip;
+	}
+
+	/**
+	 * Returns the part of the given string that is before a colon, or null if
+	 * the given String has no colon
+	 * 
+	 * @param toStrip
+	 *            The String to strip
+	 * @return The part of the String that is before a colon if there is one, or
+	 *         null if there is no colon
+	 */
+	public static String stripAttribute(String toStrip) {
+		int ind = toStrip.indexOf(SEPARATOR_CHAR);
+		if (ind < 0)
+			return null;
+
+		toStrip = toStrip.substring(0, ind);
+		toStrip = toStrip.trim();
+
+		if (toStrip.length() == 0)
+			return null;
+
+		return toStrip;
+	}
+
+	public static void setSingleValue(Text text, String value) {
+		assert (value != null);
+
+		String oldText = text.getTextNoList();
+		String attribute = stripAttribute(oldText);
+
+		if (attribute == null)
+			attribute = text.getTextNoList().trim();
+
+		text.setText(attribute + SEPARATOR_STRING + value);
+	}
+
+	public static Double getDoubleValue(String attributeValuePair) {
+		String value = stripValue(attributeValuePair);
+
+		if (value == null)
+			return null;
+
+		try {
+			return Double.parseDouble(value);
+		} catch (Exception e) {
+		}
+		return null;
+	}
+}
Index: /trunk/src/org/expeditee/gui/Browser.java
===================================================================
--- /trunk/src/org/expeditee/gui/Browser.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/Browser.java	(revision 6)
@@ -0,0 +1,236 @@
+package org.expeditee.gui;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.awt.event.WindowStateListener;
+
+import javax.swing.JFrame;
+
+import org.expeditee.actions.Actions;
+import org.expeditee.actions.Misc;
+import org.expeditee.io.Logger;
+
+/**
+ * The Main GUI class, comprises what people will see on the screen.<br>
+ * Note: Each Object (Item) is responsible for drawing itself on the screen.<br>
+ * Note2: The Frame is registered as a MouseListener and KeyListener, and
+ * processes any Events.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Browser extends JFrame implements ComponentListener,
+		WindowListener, WindowStateListener {
+	/**
+	 * Default version - just to stop eclipse from complaining about it.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	// private static final JScrollPane scrollPane = new JScrollPane();
+
+	/**
+	 * Constructs a new Browser object, then launches it
+	 * 
+	 * @param args
+	 */
+	public static void main(String[] args) {
+		Browser b = new Browser();
+		// Why do we want to ignore repaint?
+		// b.setIgnoreRepaint(true);
+		b.requestFocus();
+		// FrameGraphics.ForceRepaint();
+	}
+
+	public void setSizes(Dimension size) {
+		setSize(size);
+		setPreferredSize(size);
+		addWindowListener(this);
+		addWindowStateListener(this);
+
+		// center the frame on the screen
+		Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+		double xpos = screen.getWidth() / 2;
+		double ypos = screen.getHeight() / 2;
+		setLocation((int) (xpos - (size.getWidth() / 2)), (int) (ypos - (size
+				.getHeight() / 2)));
+
+		FrameGraphics.setMaxSize(size);
+	}
+
+	public Browser() {
+		// Use the default values initially so we can load the profile frame
+		setSizes(new Dimension(UserSettings.InitialWidth,
+				UserSettings.InitialHeight));
+
+		UserSettings.Init();
+		UserSettings.Username = System.getProperty("user.name");
+		Frame profile = FrameIO.LoadProfile(UserSettings.Username);
+		if (profile == null) {
+			profile = FrameIO.CreateNewProfile(UserSettings.Username);
+		}
+		FrameUtils.ParseProfile(profile);
+
+		// Now reset the size of the windows to the size specified in the users
+		// profile
+		setSizes(new Dimension(UserSettings.InitialWidth,
+				UserSettings.InitialHeight));
+
+		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+		addComponentListener(this);
+		pack();
+		try {
+			go();
+		} catch (Exception e) {
+			e.printStackTrace();
+			Logger.Log(e);
+		}
+		// FrameGraphics.ForceRepaint();
+	}
+
+	Graphics2D g;
+
+	/**
+	 * Loads in the Menus and the first Frame.
+	 */
+	public void go() {
+
+		Actions.Init();
+		DisplayIO.Init(this);
+		// Set visible must be just after DisplayIO.Init for the message box to
+		// be the right size
+		setVisible(true);
+
+		g = (Graphics2D) getGraphics();
+		assert g != null;
+		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+		g.setFont(g.getFont().deriveFont(40f));
+
+		FrameGraphics.setDisplayGraphics((Graphics2D) getGraphics());
+
+		// required to accept TAB key
+		setFocusTraversalKeysEnabled(false);
+
+		assert (UserSettings.FirstFrame != null);
+		Frame firstFrame = FrameIO.LoadFrame(UserSettings.FirstFrame);
+
+		DisplayIO.setCurrentFrame(firstFrame);
+
+		// Create the action handler for the
+		FrameMouseActions mouse = new FrameMouseActions();
+
+		this.addMouseListener(mouse);
+		this.addMouseMotionListener(mouse);
+		this.addKeyListener(new FrameKeyboardActions());
+		this.addMouseWheelListener(mouse);
+
+		// Rob: Why is all this being done????
+		g.dispose();
+		g = null;
+	}
+
+	/*
+	 * public void loadedMenu(String menu) { if (g == null) return;
+	 * 
+	 * g.setColor(this.getBackground()); g.fillRect(0, 0, this.getWidth(),
+	 * this.getHeight()); g.setColor(Color.BLACK); g.drawString("Loading Menu... " +
+	 * menu, this.getWidth() / 2 - 200, this .getHeight() / 2); }
+	 */
+
+	@Override
+	public void paint(Graphics g) {
+		super.paint(g);
+		FrameGraphics.Repaint();
+		//System.out.print('p');
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public void componentResized(ComponentEvent e) {
+		FrameGraphics.setMaxSize(this.getSize());
+		// FrameGraphics.Repaint();
+		repaint();
+		//System.out.println("Resize");
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public void componentMoved(ComponentEvent e) {
+		// FrameGraphics.setMaxSize(this.getSize());
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public void componentShown(ComponentEvent e) {
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public void componentHidden(ComponentEvent e) {
+	}
+
+	public int getTitleBarHeight() {
+		Rectangle contentbounds = getContentPane().getBounds();
+		contentbounds = this.getGlassPane().getBounds();
+		// contentbounds = this.getRootPane().getBounds();
+		int titleheight = getSize().height - contentbounds.height;
+
+		return titleheight;
+	}
+
+	public void windowClosing(WindowEvent e) {
+		Misc.Exit();
+	}
+
+	public void windowClosed(WindowEvent e) {
+	}
+
+	public void windowOpened(WindowEvent e) {
+	}
+
+	public void windowIconified(WindowEvent e) {
+	}
+
+	public void windowDeiconified(WindowEvent e) {
+	}
+
+	public void windowActivated(WindowEvent e) {
+		//System.out.println("Activated");
+	}
+
+	public void windowDeactivated(WindowEvent e) {
+	}
+
+	/*
+	 * public void windowGainedFocus(WindowEvent e) { FrameGraphics.Repaint(); }
+	 * 
+	 * public void windowLostFocus(WindowEvent e) { //FrameGraphics.Repaint(); }
+	 */
+	public void windowStateChanged(WindowEvent e) {
+		//FrameGraphics.Repaint();
+		//System.out.println('C');
+	}
+
+	public int getDrawingAreaX() {
+		// return scrollPane.getLocationOnScreen().x;
+		return this.getLocationOnScreen().x;
+	}
+
+	public int getDrawingAreaY() {
+		// return scrollPane.getLocationOnScreen().y;
+		return this.getLocationOnScreen().y;
+	}
+}
Index: /trunk/src/org/expeditee/gui/ColorUtils.java
===================================================================
--- /trunk/src/org/expeditee/gui/ColorUtils.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/ColorUtils.java	(revision 6)
@@ -0,0 +1,26 @@
+package org.expeditee.gui;
+
+import java.awt.Color;
+
+public class ColorUtils {
+
+	public static Color getNextColor(Color color, Color[] wheel) {
+		// search through the colour wheel to find the next colour
+		int pos = -1;
+		for (int i = 0; i < wheel.length; i++)
+			if (color == null || color.equals(wheel[i])) {
+				pos = i;
+				break;
+			}
+		// if we didnt find the color on the wheel
+		if (pos < 0) {
+			return null;
+		} else {
+			pos++;
+			pos = pos % wheel.length;
+			color = wheel[pos];
+		}
+
+		return color;
+	}
+}
Index: /trunk/src/org/expeditee/gui/DisplayIO.java
===================================================================
--- /trunk/src/org/expeditee/gui/DisplayIO.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/DisplayIO.java	(revision 6)
@@ -0,0 +1,613 @@
+package org.expeditee.gui;
+
+import java.awt.AWTException;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Robot;
+import java.awt.Toolkit;
+import java.awt.image.MemoryImageSource;
+import java.util.List;
+import java.util.Stack;
+
+import javax.swing.JOptionPane;
+
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemPermission;
+import org.expeditee.stats.SessionStats;
+
+/**
+ * This Interface is used by the Frame to control all display input and output.
+ * 
+ * @author jdm18
+ * 
+ */
+public class DisplayIO {
+
+	private static final int SMALL_CURSOR_SIZE = 16;
+
+	private static final int MEDIUM_CURSOR_SIZE = 32;
+
+	private static final int LARGE_CURSOR_SIZE = 64;
+
+	public static final Color DEFAULT_BACKGROUND = Color.WHITE;
+
+	/**
+	 * The color to be used to highlight the linked parent item, when the user
+	 * navigates backwards.
+	 */
+	public static final Color BACK_HIGHLIGHT_COLOR = Color.MAGENTA;
+
+	private static Browser _Browser;
+
+	// The current Frame being displayed on the screen.
+	private static Frame _CurrentFrames[] = new Frame[2];
+
+	// Maintains the list of frames visited thus-far for back-tracking
+	@SuppressWarnings("unchecked")
+	private static Stack<String>[] _VisitedFrames = new Stack[2];
+
+	// used to change the mouse cursor position on the screen
+	private static Robot _Robot;
+
+	// public static JMenuBar _MenuBar = new JMenuBar();
+
+	private static boolean _TwinFrames = false;
+
+	/**
+	 * The title to display in the Title bar.
+	 */
+	public static final String TITLE = "Exp13Mar2008A";
+
+	private DisplayIO() {
+	}
+
+	public static void Init(Browser browser) {
+		_Browser = browser;
+		try {
+			_Robot = new Robot();
+		} catch (AWTException e) {
+			e.printStackTrace();
+		}
+
+		Point mouse = _Browser.getMousePosition();
+		if (mouse != null) {
+			FrameMouseActions.MouseX = mouse.x;
+			FrameMouseActions.MouseY = mouse.y;
+		}
+
+		_VisitedFrames[0] = new Stack<String>();
+		_VisitedFrames[1] = new Stack<String>();
+		UpdateTitle();
+	}
+
+	public static void setTextCursor(int size) {
+		if (cursorType == Item.TEXT_CURSOR)
+			return;
+
+		cursorType = Item.TEXT_CURSOR;
+
+		//Do some stuff to adjust the cursor size based on the font size
+		final int MEDIUM_CURSOR_CUTOFF = 31;
+		final int LARGE_CURSOR_CUTOFF = 62;
+
+		int cursorSize = LARGE_CURSOR_SIZE;
+		int hotspotPos = 0;
+		int start = 0;
+
+		if (size < MEDIUM_CURSOR_CUTOFF) {
+			cursorSize = MEDIUM_CURSOR_SIZE;
+			start = cursorSize - size - 2;
+			hotspotPos = cursorSize - (size + 2) / 4;
+		} else if (size < LARGE_CURSOR_CUTOFF) {
+			hotspotPos = cursorSize - (size - 5) / 4;
+			start = cursorSize - size - 2;
+		} else {
+			int FIXED_CURSOR_MIN = 77;
+			if (size >= FIXED_CURSOR_MIN) {
+				hotspotPos = cursorSize - 2;
+			} else {
+				hotspotPos = size - (FIXED_CURSOR_MIN - cursorSize);
+			}
+		}
+
+		/*System.out.println("hs: " + hotspotPos + " start: " + start + " font: "
+				+ size + " cursor: " + (cursorSize - start));*/
+
+		int[] pixels = new int[cursorSize * cursorSize];
+
+		for (int i = start; i < cursorSize; i++)
+			pixels[i * cursorSize] = pixels[(i * cursorSize) + 1] = 0xFF000000;
+
+		Image image = Toolkit.getDefaultToolkit().createImage(
+				new MemoryImageSource(cursorSize, cursorSize, pixels, 0,
+						cursorSize));
+		Cursor textCursor = Toolkit.getDefaultToolkit().createCustomCursor(
+				image, new Point(0, hotspotPos), "textcursor");
+		_Browser.setCursor(textCursor);
+	}
+
+	/**
+	 * Sets the type of cursor the display should be using
+	 * 
+	 * @param type
+	 *            The type of cursor to display, using constants defined in the
+	 *            Cursor class.
+	 */
+	public static void setCursor(int type) {
+		// avoid flicker when not changing
+		if (type == cursorType || type == Item.UNCHANGED_CURSOR)
+			return;
+
+		cursorType = type;
+
+		if (type == Item.HIDDEN_CURSOR) {
+			int[] pixels = new int[SMALL_CURSOR_SIZE * SMALL_CURSOR_SIZE];
+			Image image = Toolkit.getDefaultToolkit().createImage(
+					new MemoryImageSource(SMALL_CURSOR_SIZE, SMALL_CURSOR_SIZE,
+							pixels, 0, SMALL_CURSOR_SIZE));
+			Cursor transparentCursor = Toolkit.getDefaultToolkit()
+					.createCustomCursor(image, new Point(0, 0),
+							"invisiblecursor");
+			_Browser.setCursor(transparentCursor);
+		} else
+			_Browser.setCursor(new Cursor(type));
+	}
+
+	private static int cursorType = Item.DEFAULT_CURSOR;
+
+	public static int getCursor() {
+		return cursorType;
+	}
+
+	/**
+	 * Moves the mouse cursor to the given x,y coordinates on the screen
+	 * 
+	 * @param x
+	 *            The x coordinate
+	 * @param y
+	 *            The y coordinate
+	 */
+	public static void setCursorPosition(int x, int y) {
+		setCursorPosition(x, y, true);
+	}
+
+	public static void setCursorPosition(int x, int y, boolean forceArrow) {
+		y += getOffset();
+
+		if (Frame.itemAttachedToCursor()) {
+			List<Item> toMove = Frame.FreeItems;
+
+			int deltax = x - FrameMouseActions.MouseX;
+			int deltay = y - FrameMouseActions.MouseY;
+
+			for (Item move : toMove) {
+				move.setPosition(move.getX() + deltax, move.getY() + deltay);
+			}
+
+			// FrameGraphics.Repaint();
+
+			// if (!item.getArea().contains(x, y))
+			// item.setPosition(x, y);
+		}
+
+		if (_TwinFrames) {
+			if (getCurrentSide() == 1)
+				x += getMiddle();
+		}
+
+		// cheat
+		FrameMouseActions.setForceArrow(forceArrow);
+
+		// ensures the cursor doesn't fall behind while waiting for robot
+		FrameMouseActions.MouseX = x;
+		FrameMouseActions.MouseY = y;
+
+		_Robot.mouseMove(_Browser.getDrawingAreaX() + x, _Browser
+				.getDrawingAreaY()
+				+ y);
+
+	}
+
+	public static void resetCursorOffset() {
+		FrameMouseActions.resetOffset();
+	}
+
+	public static void setCursorPosition(Point pos) {
+		setCursorPosition(pos.x, pos.y);
+	}
+
+	public static void setCursorPosition(Point pos, boolean forceArrow) {
+		setCursorPosition(pos.x, pos.y, forceArrow);
+	}
+
+	private static boolean _typed = false;
+
+	public static void setKeyTyped(boolean val) {
+		_typed = val;
+	}
+
+	public static boolean getKeyTyped() {
+		return _typed;
+	}
+
+	/**
+	 * Forces a complete repaint of the GUI and Frame
+	 */
+	public static void repaint() {
+		_Browser.repaint();
+	}
+
+	/**
+	 * Returns the top item (last added) of the Back-Stack (which is popped off)
+	 * 
+	 * @return The name of the last Frame added to the back-stack
+	 */
+	public static String getLastFrame() {
+		int side = getCurrentSide();
+
+		if (_VisitedFrames[side].size() > 0)
+			return _VisitedFrames[side].pop();
+		else
+			return null;
+	}
+
+	/**
+	 * Adds the given Frame to the back-stack
+	 * 
+	 * @param frame
+	 *            The Frame to add
+	 */
+	public static void addToBack(Frame toAdd) {
+		int side = getCurrentSide();
+
+		// do not allow duplicate frames
+		if (_VisitedFrames[side].size() > 0)
+			if (_VisitedFrames[side].peek().equals(toAdd.getFrameName())) {
+				return;
+			}
+
+		ItemPermission ip = FrameUtils.getCurrentItem();
+		if (ip == null || ip.Item == null)
+			_VisitedFrames[side].push(toAdd.getFrameName());
+		else
+			_VisitedFrames[side].push(toAdd.getFrameName());
+	}
+
+	/**
+	 * Returns a 'peek' at the end element on the back-stack of the current
+	 * side. If the back-stack is empty, null is returned.
+	 * 
+	 * @return The name of the most recent Frame added to the back-stack, or
+	 *         null if the back-stack is empty.
+	 */
+	public static String peekFromBack() {
+		int side = getCurrentSide();
+
+		// check that the stack is not empty
+		if (_VisitedFrames[side].size() > 0)
+			return _VisitedFrames[side].peek();
+
+		// if the stack is empty, return null
+		return null;
+	}
+
+	/**
+	 * Sets the currently displayed Frame to the given Frame
+	 * 
+	 * @param frame
+	 *            The Frame to display on the screen
+	 */
+	public static void setCurrentFrame(Frame frame) {
+		if (frame == null)
+			return;
+
+		if (_TwinFrames) {
+			if (_CurrentFrames[0] == null) {
+				_CurrentFrames[0] = frame;
+				return;
+			}
+			if (_CurrentFrames[1] == null) {
+				_CurrentFrames[1] = frame;
+				return;
+			}
+		}
+
+		// if this is already the current frame
+		if (frame == getCurrentFrame()) {
+			FrameGraphics.Repaint();
+			FrameGraphics.DisplayMessage(frame.getFrameName()
+					+ " is already the current frame.");
+			return;
+		} else {
+			SessionStats.AccessedFrame();
+		}
+
+		if (_TwinFrames) {
+			// if the same frame is being shown in both sides, load a fresh
+			// copy
+			// from disk
+			if (_CurrentFrames[getOppositeSide()] == frame
+					|| _CurrentFrames[getOppositeSide()].getOverlays()
+							.contains(new Overlay(frame, 1))) {
+				FrameIO.SuspendCache();
+				frame = FrameIO.LoadFrame(frame.getFrameName());
+				FrameIO.ResumeCache();
+			}
+
+			_CurrentFrames[getCurrentSide()] = frame;
+		} else {
+			_CurrentFrames[getCurrentSide()] = frame;
+			frame.setMaxSize(FrameGraphics.getMaxFrameSize());
+		}
+
+		FrameGraphics.Repaint();
+	}
+
+	public static void UpdateTitle() {
+		StringBuffer title = new StringBuffer(TITLE);
+
+		if (FrameGraphics.isAudienceMode())
+			title.append(" - Audience Mode");
+		// title.append(SessionStats.getShortStats());*/
+
+		_Browser.setTitle(title.toString());
+	}
+
+	public static int getCurrentSide() {
+		if (_Browser == null)
+			return 0;
+
+		if (_TwinFrames
+				&& FrameMouseActions.MouseX >= (_Browser.getWidth() / 2)
+				&& _CurrentFrames[1] != null)
+			return 1;
+
+		if (_CurrentFrames[0] == null && _CurrentFrames[1] != null)
+			return 1;
+
+		return 0;
+	}
+
+	private static int getOppositeSide() {
+		if (getCurrentSide() == 0)
+			return 1;
+
+		return 0;
+	}
+
+	public static int FrameOnSide(Frame toFind) {
+		if (_CurrentFrames[0] == toFind)
+			return 0;
+
+		if (_CurrentFrames[1] == toFind)
+			return 1;
+
+		return -1;
+	}
+
+	/**
+	 * Returns the Frame currently being displayed on the screen.
+	 * 
+	 * @return The Frame currently displayed.
+	 */
+	public static Frame getCurrentFrame() {
+		return _CurrentFrames[getCurrentSide()];
+	}
+
+	public static Frame getOppositeFrame() {
+		return _CurrentFrames[getOppositeSide()];
+	}
+
+	public static Frame[] getFrames() {
+		return _CurrentFrames;
+	}
+
+	public static int getMiddle() {
+		return _Browser.getWidth() / 2;
+	}
+
+	public static int getHeight() {
+		return _Browser.getHeight();
+	}
+
+	/**
+	 * Adds the given JMenu to the JMenuBar of the GUI
+	 * 
+	 * @param menu
+	 *            The menu to add
+	 */
+	/*
+	 * public static void addMenu(JMenu menu) { if (menu.getItemCount() == 0)
+	 * return;
+	 * 
+	 * _MenuBar.add(menu); // show progress _Browser.loadedMenu(menu.getText()); }
+	 */
+
+	/**
+	 * Performs the same action as if the user had clicked on the MenuItem at
+	 * the given index in the Menu at the given index.
+	 * 
+	 * @param menu
+	 *            The index of the menu (starts from 0)
+	 * @param index
+	 *            The index of the Item to simulate a click on (starts from 0)
+	 */
+	/*
+	 * public static void activateMenuItem(int menu, int index) { if
+	 * (_MenuBar.getComponentCount() < menu) return;
+	 * 
+	 * JMenu act = _MenuBar.getMenu(menu);
+	 * 
+	 * if (act.getComponentCount() < index) return;
+	 * 
+	 * act.getItem(index).doClick(); }
+	 * 
+	 * public static int getMenuHeight() { if (_MenuBar.getHeight() < 5) return
+	 * 21;
+	 * 
+	 * return _MenuBar.getHeight() - 5; }
+	 */
+
+	/**
+	 * 
+	 */
+	public static int getOffset() {
+		if (_Browser != null)
+			return /* getMenuHeight() + */_Browser.getTitleBarHeight();
+
+		// default offset (if the menu has not been created yet, etc)
+		return 0;
+	}
+
+	/**
+	 * Returns the current mouse X coordinate. This coordinate is relative to
+	 * the left edge of the frame the mouse is in. It takes into account the
+	 * user being in twin frames mode.
+	 * 
+	 * @return The X coordinate of the mouse.
+	 */
+	public static int getMouseX() {
+		if (_TwinFrames && getMouseY() < FrameGraphics.getMaxSize().height)
+			return FrameMouseActions.MouseX % (_Browser.getWidth() / 2);
+
+		return FrameMouseActions.MouseX;
+	}
+
+	/**
+	 * Returns the current mouse Y coordinate. This is relative to the top of
+	 * the frame.
+	 * 
+	 * @return The Y coordinate of the mouse.
+	 */
+	public static int getMouseY() {
+		return FrameMouseActions.MouseY - getOffset();
+	}
+
+	public static int getRealMouseX() {
+		return FrameMouseActions.MouseX;
+	}
+
+	public static int getRealMouseY() {
+		return FrameMouseActions.MouseY;
+	}
+
+	public static void Back() {
+		int side = getCurrentSide();
+
+		// there must be a frame to go back to
+		if (_VisitedFrames[side].size() < 1) {
+			FrameGraphics.DisplayMessage("You are already on the home frame");
+			return;
+		}
+
+		if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
+			FrameGraphics.DisplayMessage("Back operation cancelled");
+			return;
+		}
+
+		String oldFrame = getCurrentFrame().getFrameName().toLowerCase();
+
+		// do not get a cached version (in case it is in the other window)
+		if (isTwinFramesOn())
+			FrameIO.SuspendCache();
+		Frame frame = FrameIO.LoadFrame(_VisitedFrames[side].pop());
+		if (isTwinFramesOn())
+			FrameIO.ResumeCache();
+
+		FrameUtils.DisplayFrame(frame, false);
+		FrameMouseActions.setHighlightHold(true);
+
+		for (Item i : frame.getItems())
+			if (i.getLink() != null
+					&& i.getLink().toLowerCase().equals(oldFrame)) {
+				i.showHighlight(true, BACK_HIGHLIGHT_COLOR);
+			}
+
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Toggles the display of frames between TwinFrames mode and Single frame
+	 * mode.
+	 */
+	public static void ToggleTwinFrames() {
+		// determine which side is the active side
+		int opposite = getOppositeSide();
+		int current = getCurrentSide();
+		_TwinFrames = !_TwinFrames;
+
+		// if TwinFrames is being turned on
+		if (_TwinFrames) {
+			// if this is the first time TwinFrames has been toggled on,
+			// load
+			// the user's first frame
+			if (_VisitedFrames[opposite].size() == 0) {
+				FrameIO.SuspendCache();
+				setCurrentFrame(FrameIO.LoadFrame(UserSettings.FirstFrame));
+				FrameIO.ResumeCache();
+			} else {
+				// otherwise, restore the frame from the side's back-stack
+				setCurrentFrame(FrameIO.LoadFrame(_VisitedFrames[opposite]
+						.pop()));
+			}
+
+			// else, TwinFrames is being turned off
+		} else {
+			// add the frame to the back-stack
+			Frame hiding = _CurrentFrames[opposite];
+			FrameUtils.LeavingFrame(hiding);
+			_VisitedFrames[opposite].add(hiding.getFrameName());
+			_CurrentFrames[opposite] = null;
+			_CurrentFrames[current].setMaxSize(FrameGraphics.getMaxFrameSize());
+		}
+		if (_CurrentFrames[current] != null)
+			_CurrentFrames[current].setMaxSize(FrameGraphics.getMaxFrameSize());
+		if (_CurrentFrames[opposite] != null)
+			_CurrentFrames[opposite]
+					.setMaxSize(FrameGraphics.getMaxFrameSize());
+
+		FrameGraphics.Clear();
+		FrameGraphics.Repaint();
+	}
+
+	public static boolean isTwinFramesOn() {
+		return _TwinFrames;
+	}
+
+	public static void Reload(int side) {
+		if (side < 0)
+			return;
+
+		FrameIO.SuspendCache();
+		_CurrentFrames[side] = FrameIO.LoadFrame(_CurrentFrames[side]
+				.getFrameName());
+		FrameIO.ResumeCache();
+	}
+
+	public static boolean DisplayConfirmDialog(String message, String title,
+			int type, int options, int res) {
+		return JOptionPane.showConfirmDialog(_Browser, message, title, options,
+				type) == res;
+	}
+
+	public static final int RESULT_OK = JOptionPane.OK_OPTION;
+
+	public static final int OPTIONS_OK_CANCEL = JOptionPane.OK_CANCEL_OPTION;
+
+	public static final int TYPE_WARNING = JOptionPane.WARNING_MESSAGE;
+
+	public static void pressMouse(int buttons) {
+		_Robot.mousePress(buttons);
+	}
+
+	public static void releaseMouse(int buttons) {
+		_Robot.mouseRelease(buttons);
+	}
+
+	public static void clickMouse(int buttons) {
+		_Robot.mousePress(buttons);
+		_Robot.mouseRelease(buttons);
+	}
+}
Index: /trunk/src/org/expeditee/gui/Frame.java
===================================================================
--- /trunk/src/org/expeditee/gui/Frame.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/Frame.java	(revision 6)
@@ -0,0 +1,1307 @@
+package org.expeditee.gui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.Polygon;
+import java.awt.image.ImageObserver;
+import java.awt.image.VolatileImage;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+
+import org.expeditee.io.Conversion;
+import org.expeditee.io.Logger;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Line;
+import org.expeditee.items.Text;
+
+/**
+ * Represents a KMS Frame that is displayed on the screen. Also is a registered
+ * MouseListener on the Browser, and processes any MouseEvents directly.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Frame implements ImageObserver {
+
+	public static final Color[] COLOR_WHEEL = { Color.BLACK, Color.GRAY,
+			new Color(235, 235, 235), new Color(225, 225, 255),
+			new Color(195, 255, 255), new Color(225, 255, 225),
+			new Color(255, 255, 195), new Color(255, 225, 225),
+			new Color(255, 195, 255), null };
+
+	// The various attributes of this Frame
+	/**
+	 * TODO: Change these to non-string attributes (where applicable). All
+	 * processing to\from Java types should be done in KMSReader and KMSWriter.
+	 */
+	private String _frameset = null;
+
+	private int _number = -1;
+
+	private int _version = 1;
+
+	private int _fversion = -1;
+
+	private String _protection = null;
+
+	private String _owner = null;
+
+	private String _creationDate = null;
+
+	private String _modifiedUser = null;
+
+	private String _modifiedDate = null;
+
+	private String _frozenDate = null;
+
+	private Color _background;
+
+	private Color _foreground;
+
+	public String path;
+
+	private boolean _sorted = true;
+
+	// The items contained in this Frame
+
+	// records whether a change has been made to this Frame (for saving
+	// purposes).
+	private boolean _change = false;
+
+	private boolean _saved = false;
+
+	// list of deleted items that can be restored
+	private Stack<Item> _undo = new Stack<Item>();
+
+	// basically just a list of smaller objects?
+	// maybe a hashtable (id -> item?)
+	// Note: Needs to be able to be iterated through (for painting)
+	private List<Item> _body = new ArrayList<Item>();
+
+	public static List<Item> FreeItems = new ArrayList<Item>();
+
+	private int _lineCount = 0;
+
+	private int _itemCount = 1;
+
+	// The frameName to display on the screen
+	private Text _frameName = null;
+
+	// private Text _template = UserSettings.ItemTemplate.copy();
+
+	private List<Overlay> _overlays = new ArrayList<Overlay>();
+
+	private VolatileImage _buffer = null;
+
+	private boolean _validBuffer = true;
+
+	/**
+	 * Default constructor, nothing is set.
+	 */
+	public Frame() {
+		// _template.setParent(this);
+	}
+
+	public VolatileImage getBuffer() {
+		return _buffer;
+	}
+
+	public void setBuffer(VolatileImage newBuffer) {
+		_buffer = newBuffer;
+		// setBufferValid(true);
+	}
+
+	public boolean isBufferValid() {
+		if (_buffer != null && _buffer.contentsLost())
+			return false;
+
+		return _validBuffer;
+	}
+
+	public void setBufferValid(boolean newValue) {
+		_validBuffer = newValue;
+	}
+
+	public int getNextItemID() {
+		return ++_itemCount;
+	}
+
+	public void updateIDs(List<Item> items) {
+		for (Item i : items)
+			if (!(i instanceof Line))
+				i.setID(getNextItemID());
+			else
+				i.setID(++_lineCount);
+	}
+
+	/**
+	 * Returns whether this Frame has been changed and required saving to disk.
+	 * 
+	 * @return True if this Frame has been altered, false otherwise.
+	 */
+	public boolean hasChanged() {
+		// virtual frames are never saved
+		if (_number == -1)
+			return false;
+
+		return _change;
+	}
+
+	/**
+	 * Sets whether this Frame should be saved to disk.
+	 * 
+	 * @param value
+	 *            False if this Frame should be saved to disk, False otherwise.
+	 */
+	public void setChanged(boolean value) {
+		_change = value;
+
+		if (_change)
+			setBufferValid(false);
+	}
+
+	// indicates the frame has changed
+	private void change() {
+		setChanged(true);
+	}
+
+	/**
+	 * Returns an ArrayList of all Items currently on the Frame (excludes Items
+	 * attached to the cursor).
+	 * 
+	 * @return The list of Item objects that are on this Frame.
+	 */
+	public List<Item> getItems() {
+
+		if (!_sorted) {
+			Collections.sort(_body);
+			_sorted = true;
+		}
+
+		return _body;
+	}
+
+	/**
+	 * Returns a list of all the non annotation text items on the frame which
+	 * are not the title or frame name.
+	 * 
+	 * @return the list of body text items.
+	 */
+	public List<Text> getBodyTextItems() {
+		List<Text> bodyTextItems = new ArrayList<Text>();
+		Text frameTitle = getTitle();
+
+		for (Item i : getItems()) {
+			// only add up normal body text items
+			if (i instanceof Text && i != _frameName && i != frameTitle
+					&& !i.isAnnotation()) {
+				bodyTextItems.add((Text) i);
+			}
+		}
+
+		return bodyTextItems;
+	}
+
+	/**
+	 * Gets the last item on the frame that is a non annotation item but is also
+	 * text.
+	 * 
+	 * @return the last non annotation text item.
+	 */
+	public Text getLastNonAnnotationTextItem() {
+		List<Item> items = getItems();
+
+		// find the last non-annotation text item
+		for (int i = (items.size() - 1); i >= 0; i--) {
+			Item it = items.get(i);
+
+			if (it instanceof Text && !it.isAnnotation()) {
+				return (Text) it;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Iterates through the list of items on the frame, and returns one with the
+	 * given id if one exists, otherwise returns null.
+	 * 
+	 * @param id
+	 *            The id to search for in the list of items
+	 * @return The item on this frame with the given ID, or null if one is not
+	 *         found.
+	 */
+	public Item getItemWithID(int id) {
+		for (Item i : _body)
+			if (i.getID() == id)
+				return i;
+
+		return null;
+	}
+
+	/**
+	 * Sets this Frame's Title which is displayed in the top left corner.
+	 * 
+	 * @param title
+	 *            The title to assign to this Frame
+	 */
+	public void setTitle(String title) {
+		if (title == null || title == "")
+			return;
+
+		boolean oldchange = _change;
+
+		// remove any numbering this title has
+		title = title.replaceAll("^\\d*[.] *", "");
+		Text frameTitle = getTitle();
+
+		if (frameTitle == null) {
+			if (UserSettings.TitleTemplate == null) {
+				frameTitle = new Text(getNextItemID(), title);
+				frameTitle.setPosition(Item.MARGIN_LEFT
+						+ org.expeditee.io.Conversion.X_ADJUST,
+						org.expeditee.io.Conversion.Y_ADJUST);
+			} else {
+				frameTitle = UserSettings.TitleTemplate.copy();
+				frameTitle.setID(this.getNextItemID());
+				frameTitle.setText(title);
+			}
+			addItem(frameTitle);
+		} else {
+			frameTitle.setText(title);
+			title = ItemUtils.StripTagSymbol(title);
+			String autoBulletText = FrameKeyboardActions.getAutoBullet(title);
+			if (autoBulletText.length() > 0)
+				frameTitle.stripFirstWord();
+		}
+
+		FrameUtils.Parse(this);
+
+		// do not save if this is the only change
+		setChanged(oldchange);
+	}
+
+	public Text getTitle() {
+		List<Item> items = getItems();
+		for (Item i : items) {
+			if (i instanceof Text /* && !i.isAnnotation() */&& i != _frameName)
+				return (Text) i;
+		}
+
+		return null;
+	}
+
+	public Text getName() {
+		return _frameName;
+	}
+
+	public Text getItemTemplate() {
+		Text t = UserSettings.ItemTemplate;
+
+		// check for an updated template...
+		for (Item i : this.getItems()) {
+			if (ItemUtils.isTag(i, ItemUtils.TAG_ITEM_TEMPLATE)) {
+				t = (Text) i;
+				break;
+			}
+		}
+
+		// If the item is linked apply any attribute pairs on the child frame
+		String link = t.getAbsoluteLink();
+		// need to get link first because copy doesnt copy the link
+		t = t.copy();
+		if (link != null) {
+			t.setLink(null);
+			Frame childFrame = FrameIO.LoadFrame(link);
+			if (childFrame != null) {
+				// read in attribute value pairs
+				for (Text attribute : childFrame.getBodyTextItems()) {
+					AttributeUtils.SetAttribute(t, attribute);
+				}
+			}
+		}
+
+		return t;
+	}
+
+	public Text getAnnotationTemplate() {
+		Text t = null;
+		// check for an updated template...
+		for (Item i : this.getItems()) {
+			if (ItemUtils.isTag(i, ItemUtils.TAG_ANNOTATION_TEMPLATE)) {
+				t = (Text) i;
+				break;
+			}
+		}
+
+		if (t == null) {
+			if (UserSettings.AnnotationTemplate != null)
+				t = UserSettings.AnnotationTemplate;
+			else
+				t = getItemTemplate();
+		}
+
+		return t.copy();
+	}
+
+	public Text getCodeCommentTemplate() {
+		Text t = null;
+
+		// check for an updated template...
+		for (Item i : this.getItems()) {
+			if (ItemUtils.isTag(i, ItemUtils.TAG_CODE_COMMENT_TEMPLATE)) {
+				t = (Text) i;
+				break;
+			}
+		}
+
+		if (t == null) {
+			if (UserSettings.CodeCommentTemplate != null)
+				t = UserSettings.CodeCommentTemplate;
+			else
+				t = getItemTemplate();
+		}
+
+		return t.copy();
+	}
+
+	/*
+	 * public void setItemTemplate(Text template) { if (template != null)
+	 * _template = template; else _template = UserSettings.ItemTemplate.copy(); }
+	 */
+
+	/**
+	 * Returns any items on this frame that are within the given Shape. Also
+	 * returns any Items on overlay frames that are within the Shape.
+	 * 
+	 * @param shape
+	 *            The Shape to search for Items in
+	 * @return All Items on this Frame or overlayed Frames for which
+	 *         Item.intersects(shape) return true.
+	 */
+	public List<Item> getItemsWithin(Polygon poly) {
+		ArrayList<Item> results = new ArrayList<Item>();
+
+		for (Item i : _body)
+			if (i.intersects(poly)) {// || shape.contains(i.getX(),
+				// i.getY())){
+				if (!results.contains(i))
+					results.add(i);
+				/*
+				 * List<Item> connected = i.getConnected(); for(Item item :
+				 * connected) if(!results.contains(item))
+				 * results.add(item);//All(i.getConnected());
+				 */
+			}
+
+		for (Overlay o : _overlays)
+			results.addAll(o.Frame.getItemsWithin(poly));
+
+		/*
+		 * ArrayList<Dot> toInclude = new ArrayList<Line>(0); //remove lines
+		 * that do not have a point anchored in the polygon for(Item i :
+		 * results){ if(i instanceof Line){ Line line = (Line) i;
+		 * if(!(results.contains(line.getStartDot())) &&
+		 * !(results.contains(line.getEndDot()))) removeLines.add(line); } }
+		 * 
+		 * results.removeAll(removeLines);
+		 */
+
+		return results;
+	}
+
+	/**
+	 * Sets the name of this Frame to the given String, to be displayed in the
+	 * upper right corner.
+	 * 
+	 * @param name
+	 *            The name to use for this Frame.
+	 */
+	public void setFrameset(String name) {
+		_frameset = name;
+	}
+
+	/**
+	 * Adjusts frameset names so that a period is appended if they end in a
+	 * digit.
+	 * 
+	 * @param name
+	 *            the unadjusted frame set name
+	 * @return the adjusted frame set name
+	 */
+	public String getFramesetNameAdjusted() {
+		return _frameset
+				+ (Character.isDigit(_frameset.charAt(_frameset.length() - 1)) ? "."
+						: "");
+	}
+
+	/**
+	 * Adjusts a frameset name so a period is appended if it ends in a digit.
+	 * This form of the frameset name is used when referencing frames in the
+	 * frame set.
+	 * 
+	 * @param frameset
+	 * @return
+	 */
+	public static String GetFramesetNameAdjusted(String frameset) {
+		return frameset
+				+ (Character.isDigit(frameset.charAt(frameset.length() - 1)) ? "."
+						: "");
+	}
+
+	public void setFrameName(String framename) {
+		int num = Conversion.getFrameNumber(framename);
+		String frameset = Conversion.getFrameset(framename, false);
+
+		setFrameset(frameset);
+		setFrameNumber(num);
+	}
+
+	/**
+	 * Sets the frame number of this Frame to the given integer
+	 * 
+	 * @param number
+	 *            The number to set as the frame number
+	 */
+	public void setFrameNumber(int number) {
+		_number = number;
+		boolean oldchange = _change;
+
+		int id;
+
+		if (_frameName != null) {
+			removeItem(_frameName);
+			id = _frameName.getID();
+		} else
+			id = -1 * getNextItemID();
+
+		_frameName = new Text(id);
+		_frameName.setText(getFramesetNameAdjusted() + _number);
+		_frameName.setPosition(FrameGraphics.getMaxFrameSize().width
+				- _frameName.getBoundsWidth() - Item.MARGIN_RIGHT
+				- org.expeditee.io.Conversion.X_ADJUST,
+				org.expeditee.io.Conversion.Y_ADJUST);
+		addItem(_frameName);
+
+		setChanged(oldchange);
+	}
+
+	/**
+	 * Returns the number of this Frame.
+	 * 
+	 * @return The Frame number of this Frame or -1 if it is not set.
+	 */
+	public int getFrameNumber() {
+		return _number;
+	}
+
+	/**
+	 * Sets the version of this Frame to the given String.
+	 * 
+	 * @param version
+	 *            The version to use for this Frame.
+	 */
+	public void setVersion(int version) {
+		_version = version;
+	}
+
+	/**
+	 * Sets the format version of this Frame to the given String.
+	 * 
+	 * @param version
+	 *            The format version to use for this Frame.
+	 */
+	public void setFormatVersion(int version) {
+		_fversion = version;
+	}
+
+	/**
+	 * Sets the protection of this Frame to the given String.
+	 * 
+	 * @param protection
+	 *            The protection to use for this Frame.
+	 */
+	public void setProtection(String protection) {
+		_protection = protection;
+	}
+
+	/**
+	 * Sets the owner of this Frame to the given String.
+	 * 
+	 * @param owner
+	 *            The owner to use for this Frame.
+	 */
+	public void setOwner(String owner) {
+		_owner = owner;
+	}
+
+	/**
+	 * Sets the created date of this Frame to the given String.
+	 * 
+	 * @param date
+	 *            The date to use for this Frame.
+	 */
+	public void setDateCreated(String date) {
+		_creationDate = date;
+		_modifiedDate = date;
+		for (Item i : _body) {
+			i.setDateCreated(date);
+		}
+	}
+
+	public void resetDateCreated() {
+		setDateCreated(Logger.EasyDateFormat("ddMMMyyyy:HHmm"));
+	}
+
+	/**
+	 * Sets the last modifying user of this Frame to the given String.
+	 * 
+	 * @param user
+	 *            The user to set as the last modifying user.
+	 */
+	public void setLastModifyUser(String user) {
+		_modifiedUser = user;
+	}
+
+	/**
+	 * Sets the last modified date of this Frame to the given String.
+	 * 
+	 * @param date
+	 *            The date to set as the last modified date.
+	 */
+	public void setLastModifyDate(String date) {
+		_modifiedDate = date;
+	}
+
+	/**
+	 * Sets the last frozen date of this Frame to the given String.
+	 * 
+	 * @param date
+	 *            The date to set as the last frozen date.
+	 */
+	public void setFrozenDate(String date) {
+		_frozenDate = date;
+	}
+
+	public void setResort(boolean value) {
+		_sorted = !value;
+	}
+
+	/**
+	 * Adds the given Item to the body of this Frame.
+	 * 
+	 * @param item
+	 *            The Item to add to this Frame.
+	 */
+	public void addItem(Item item) {
+		if (item != null) {
+			if (_body.contains(item)) {
+				System.out.println("Item (" + item.getClass().getSimpleName()
+						+ ") with ID " + item.getID() + " already in body.");
+				return;
+			}
+
+			if (item instanceof Line)
+				_lineCount++;
+
+			_itemCount = Math.max(_itemCount, item.getID());
+
+			_body.add(item);
+			item.setParent(this);
+
+			_sorted = false;
+
+			item.setMaxSize(FrameGraphics.getMaxFrameSize());
+			change();
+		}
+	}
+
+	public void setMaxSize(Dimension max) {
+		if (max == null)
+			return;
+
+		for (Item i : _body)
+			i.setMaxSize(max);
+
+		_frameName.setPosition(FrameGraphics.getMaxFrameSize().width
+				- _frameName.getBoundsWidth() - Item.MARGIN_RIGHT
+				- org.expeditee.io.Conversion.X_ADJUST,
+				org.expeditee.io.Conversion.Y_ADJUST);
+	}
+
+	public void addAllItems(List<Item> toAdd) {
+		for (Item i : toAdd)
+			addItem(i);
+	}
+
+	public void removeAllItems(List<Item> toRemove) {
+		for (Item i : toRemove)
+			removeItem(i);
+	}
+
+	public void removeItem(Item item) {
+		if (_body.remove(item))
+			change();
+	}
+
+	/**
+	 * Adds the given list of Items to the undo stack. This is the same as
+	 * calling addToUndo() for each Item in the list.
+	 * 
+	 * @param items
+	 *            The List of Items to add to the undo stack.
+	 */
+	public void addAllToUndo(List<Item> items) {
+		if (items.size() < 1)
+			return;
+
+		String id = "" + _undo.size();
+
+		for (Item i : items) {
+			i.setData(id);
+			_undo.push(i);
+		}
+	}
+
+	public void addToUndo(Item item) {
+		if (item == null)
+			return;
+
+		item.setData("" + _undo.size());
+		_undo.push(item);
+	}
+
+	public void undo() {
+		Item undo = null;
+
+		if (_undo.size() <= 0)
+			return;
+
+		undo = _undo.pop();
+
+		// if the change was to characteristics
+		if (_body.contains(undo)) {
+			Item old = _body.get(_body.indexOf(undo));
+			_body.set(_body.indexOf(old), undo);
+			// the item was deleted
+		} else {
+			List<Item> toRestore = new LinkedList<Item>();
+			toRestore.add(undo);
+
+			// remove any connected items at the top of the stack
+			while (_undo.size() > 0) {
+				Item next = _undo.peek();
+				// if this item was connected to one already picked up, remove
+				// it from the stack
+				if (toRestore.contains(next))
+					_undo.pop();
+				// else, if this item should be restored (deleted in an enclosed
+				// set)
+				else if (next.getData().equals(undo.getData())) {
+					_undo.pop();
+					toRestore.add(next);
+					// otherwise, we are done
+				} else
+					break;
+			}
+
+			// if these items were deleted from a frame, add them
+			addAllItems(toRestore);
+
+			for (Item i : toRestore) {
+				if (i instanceof Line) {
+					Line line = (Line) i;
+					line.getStartItem().addLine(line);
+					line.getEndItem().addLine(line);
+				}
+			}
+		}
+
+		change();
+		FrameGraphics.Repaint();
+		ItemUtils.EnclosedCheck(_body);
+	}
+
+	/**
+	 * Returns the frameset of this Frame
+	 * 
+	 * @return The name of this Frame's frameset.
+	 */
+	public String getFramesetName() {
+		return _frameset;
+	}
+
+	public String getFrameName() {
+		return getFramesetNameAdjusted() + _number;
+	}
+
+	public Item getFrameNameItem() {
+		return _frameName;
+	}
+
+	/**
+	 * Returns the format version of this Frame
+	 * 
+	 * @return The version of this Frame.
+	 */
+	public int getVersion() {
+		return _version;
+	}
+
+	public int getFormatVersion() {
+		return _fversion;
+	}
+
+	public String getProtection() {
+		return _protection;
+	}
+
+	public String getOwner() {
+		return _owner;
+	}
+
+	public String getDateCreated() {
+		return _creationDate;
+	}
+
+	public String getLastModifyUser() {
+		return _modifiedUser;
+	}
+
+	public String getLastModifyDate() {
+		return _modifiedDate;
+	}
+
+	public String getFrozenDate() {
+		return _frozenDate;
+	}
+
+	public void setBackgroundColor(Color back) {
+		_background = back;
+		change();
+		FrameGraphics.Repaint();
+	}
+
+	public Color getBackgroundColor() {
+		return _background;
+	}
+
+	public Color getPaintBackgroundColor() {
+		if (_background == null)
+			return DisplayIO.DEFAULT_BACKGROUND;
+
+		return _background;
+	}
+
+	public void setForegroundColor(Color front) {
+		_foreground = front;
+		change();
+		FrameGraphics.Repaint();
+	}
+
+	public Color getForegroundColor() {
+		return _foreground;
+	}
+
+	public Color getPaintForegroundColor() {
+		final int GRAY = 127;
+		final int THRESHOLD = 10;
+
+		if (_foreground == null) {
+			Color back = getPaintBackgroundColor();
+			if (Math.abs(back.getRed() - GRAY) < THRESHOLD
+					&& Math.abs(back.getBlue() - GRAY) < THRESHOLD
+					&& Math.abs(back.getGreen() - GRAY) < THRESHOLD)
+				return Color.WHITE;
+
+			Color fore = new Color(Math.abs(255 - back.getRed()), Math
+					.abs(255 - back.getGreen()), Math.abs(255 - back.getBlue()));
+			return fore;
+			// return Item.DEFAULT_FOREGROUND;
+		}
+
+		return _foreground;
+	}
+
+	public String toString() {
+		String s = "";
+		s += "Name: " + _frameset + _number + "\n";
+		s += "Version: " + _version + "\n";
+		s += "Format Version: " + _fversion + "\n";
+		s += "Protection: " + _protection + "\n";
+		s += "Owner: " + _owner + "\n";
+		s += "Date Created: " + _creationDate + "\n";
+		s += "Last Mod. User: " + _modifiedUser + "\n";
+		s += "Last Mod. Date: " + _modifiedDate + "\n";
+		s += "Last Froz. Date: " + _frozenDate + "\n";
+
+		s += "\n";
+		s += "\n";
+
+		s += "Items: " + _body.size() + "\n";
+
+		return s;
+	}
+
+	public Item getItemAbove(Item current) {
+		int ind = _body.indexOf(current);
+		if (ind == -1)
+			return null;
+
+		// loop through all items above this one, return the first match
+		for (int i = ind - 1; i >= 0; i--) {
+			Item check = _body.get(i);
+
+			if (FrameUtils.inSameColumn(check, current))
+				return check;
+		}
+
+		return null;
+	}
+
+	/**
+	 * Updates any Images that require it from their ImageObserver (Principally
+	 * Animated GIFs)
+	 */
+	public boolean imageUpdate(Image img, int infoflags, int x, int y,
+			int width, int height) {
+		FrameGraphics.ForceRepaint();
+
+		if (DisplayIO.getCurrentFrame() == this)
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * Gets the text items that are in the same column and below a specified
+	 * item. Frame title and name are excluded from the column list.
+	 * 
+	 * @param from
+	 *            The Item to get the column for.
+	 */
+	public List<Item> getColumn(Item from) {
+		// Check that this item is on the current frame
+		if (!_body.contains(from))
+			return null;
+
+		// Make sure the items are sorted
+		Collections.sort(_body);
+
+		if (from == null) {
+			from = getLastNonAnnotationTextItem();
+		}
+
+		if (from == null)
+			return null;
+
+		/**
+		 * TODO: Check
+		 */
+
+		List<Item> column = new ArrayList<Item>();
+
+		// Create a list of items consisting of the item 'from' and all the
+		// items below it which are also in the same column as it
+		for (int i = _body.indexOf(from); i < _body.size(); i++) {
+			Item item = _body.get(i);
+			if (isNormalTextItem(item)) {
+				if (FrameUtils.inSameColumn(from, item))
+					column.add(item);
+			}
+		}
+
+		return column;
+	}
+
+	/**
+	 * Adds the given Frame to the list of overlays Frames being drawn with this
+	 * Frame.
+	 * 
+	 * @param overlay
+	 *            The Frame to add
+	 */
+	public void addOverlay(Overlay overlay) {
+		if (_overlays == null || overlay == null)
+			_overlays = new ArrayList<Overlay>();
+
+		_overlays.add(overlay);
+	}
+
+	/**
+	 * Removes the given Frame from the list of overlay Frames being drawn with
+	 * this Frame.
+	 * 
+	 * @param overlay
+	 *            The Frame to remove
+	 */
+	public void removeOverlay(Frame overlay) {
+		_overlays.remove(overlay);
+		FrameGraphics.Repaint();
+	}
+
+	public List<Overlay> getOverlays() {
+		return _overlays;
+	}
+
+	public void clearOverlays() {
+		_overlays.clear();
+	}
+
+	public void addAllOverlays(List<Overlay> overlays) {
+		_overlays.addAll(overlays);
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof String) {
+			return (String.CASE_INSENSITIVE_ORDER.compare((String) o,
+					getFrameName()) == 0);
+		}
+
+		if (o instanceof Frame) {
+			return getFrameName().equals(((Frame) o).getFrameName());
+		}
+
+		return super.equals(o);
+	}
+
+	/**
+	 * Merge one frames contents into another.
+	 * 
+	 * @param toMergeWith
+	 */
+	private void merge(Frame toMergeWith) {
+		if (toMergeWith == null)
+			return;
+
+		List<Item> copies = ItemUtils.CopyItems(toMergeWith.getItems());
+
+		for (Item i : copies) {
+			if (i.getID() >= 0) {
+				i.setID(this.getNextItemID());
+				addItem(i);
+			}
+		}
+	}
+
+	/**
+	 * TODO document what this method is actually doing and why Merge text?!?!
+	 * 
+	 * @param toMerge
+	 * @return the items that cant be merged?!?!
+	 */
+	public List<Item> merge(List<Item> toMerge) {
+		ArrayList<Item> remain = new ArrayList<Item>(0);
+
+		for (Item i : toMerge) {
+			if (!(i instanceof Text))
+				remain.add(i);
+			else {
+				if (!AttributeUtils.SetAttribute(this, (Text) i)) {
+					if (i.getLink() != null)
+						merge(FrameIO.LoadFrame(i.getAbsoluteLink()));
+					else if (FrameIO
+							.isValidFrameName(((Text) i).getFirstLine())) {
+						merge(FrameIO.LoadFrame(((Text) i).getFirstLine()));
+					}
+				}
+			}
+		}
+
+		return remain;
+	}
+
+	/**
+	 * Removes all non-title non-annotation items from this Frame. All removed
+	 * items are added to the backup-stack.
+	 */
+	public void clear() {
+		List<Item> newBody = new ArrayList<Item>(0);
+
+		for (Item i : _body)
+			if (i.isAnnotation() || i == getName() || i == getTitle())
+				newBody.add(i);
+
+		_body.removeAll(newBody);
+		addAllToUndo(_body);
+		_body = newBody;
+	}
+
+	/**
+	 * Creates a new Text Item with no text. The newly created Item is a copy
+	 * the ItemTemplate if one is present, and inherits all the attributes of
+	 * the Template
+	 * 
+	 * @return The newly created Text Item
+	 */
+	public Text createNewText(String text) {
+		Text t = createBlankText(text);
+		t.setText(text);
+
+		return t;
+	}
+
+	public Text createBlankText(String templateType) {
+		Text t;
+		if (templateType.length() == 0)
+			t = getItemTemplate().copy();
+		else
+			t = getItemTemplate(templateType.charAt(0));
+
+		// reset attributes
+		t.setID(getNextItemID());
+		t.setMaxSize(FrameGraphics.getMaxFrameSize());
+		t.setPosition(DisplayIO.getMouseX(), DisplayIO.getMouseY());
+		t.setText("");
+		return t;
+	}
+
+	private Text getItemTemplate(char firstChar) {
+		switch (firstChar) {
+		case '@':
+			return getAnnotationTemplate();
+		case '/':
+			return getCodeCommentTemplate();
+		default:
+			return getItemTemplate();
+		}
+	}
+
+	public Text createNewText() {
+		return createNewText("");
+	}
+
+	public Text addText(int x, int y, String text, String action) {
+		Text t = createNewText(text);
+		t.setPosition(x, y);
+		t.addAction(action);
+
+		addItem(t);
+		return t;
+	}
+
+	public Text addText(int x, int y, String text, String action, String link) {
+		Text t = addText(x, y, text, action);
+		t.setLink(link);
+		return t;
+	}
+
+	public Dot addDot(int x, int y) {
+		Dot d = new Dot(x, y, getNextItemID());
+		addItem(d);
+		return d;
+	}
+
+	/**
+	 * Creates a JMenu from the Items present on this Frame and adds it to the
+	 * Menus on the Display
+	 */
+	/*
+	 * public void createMenu() { for (Item i : _body) { if (ItemUtils.isTag(i,
+	 * ItemUtils.TAG_MENU) && i.getLink() != null) { Frame nextMenu =
+	 * FrameIO.LoadFrame(i.getLink()); JMenu menu = nextMenu.createSubMenu();
+	 * DisplayIO.addMenu(menu); } } }
+	 */
+
+	/**
+	 * Creates a new JMenu from the Items present on this Frame
+	 * 
+	 * @return The newly created JMenu
+	 */
+	/*
+	 * private JMenu createSubMenu() { Text frameTitle = getTitle();
+	 * 
+	 * if (frameTitle == null) FrameUtils.Parse(this);
+	 * 
+	 * if (frameTitle == null || frameTitle.getFirstLine() == null) return null;
+	 * 
+	 * JMenu menu = new JMenu(frameTitle.getFirstLine());
+	 * 
+	 * boolean literal = false; String litTitle = null;
+	 * 
+	 * for (Item i : _body) { // if the @lit tag is active if (literal) {
+	 * literal = false; if (litTitle == null) litTitle = ((Text)
+	 * i).getFirstLine(); JMenuItem item = new JMenuItem(litTitle);
+	 * item.addActionListener(new MenuAction(i, true)); menu.add(item); //
+	 * non-annotation items with links or actions are put on the // menu } else
+	 * if (i != _frameName && i instanceof Text && !i.isAnnotation() &&
+	 * (i.getLink() != null || i.getAction() != null)) { JMenuItem item = new
+	 * JMenuItem(((Text) i).getText().get(0)); item.addActionListener(new
+	 * MenuAction(i, false)); menu.add(item); // if this is another menu frame }
+	 * else if (i.getLink() != null && ItemUtils.isTag(i, ItemUtils.TAG_MENU)) {
+	 * Frame nextMenu = FrameIO.LoadFrame(i.getLink());
+	 * menu.add(nextMenu.createSubMenu()); // if this menu is continued on
+	 * another frame } else if (i.getLink() != null && ItemUtils.isTag(i,
+	 * ItemUtils.TAG_MENU_NEXT)) {
+	 * 
+	 * Frame nextMenu = FrameIO.LoadFrame(i.getLink()); JMenu nmenu =
+	 * nextMenu.createSubMenu(); for(int m = 0; m < nmenu.getComponentCount();
+	 * m++) menu.add(nmenu.getComponent(m)); // if this is a @lit tag, then the
+	 * next item is copied } else if (ItemUtils.isTag(i, ItemUtils.TAG_LITERAL)) {
+	 * literal = true; String title = ItemUtils.StripTag(((Text)
+	 * i).getFirstLine(), ItemUtils.GetTag(ItemUtils.TAG_LITERAL)); if
+	 * (title.length() > 0) litTitle = title; else litTitle = null; } }
+	 * 
+	 * return menu; }
+	 */
+
+	/*
+	 * private class MenuAction implements ActionListener { private Item _source =
+	 * null;
+	 * 
+	 * private boolean _copy = false;
+	 * 
+	 * public MenuAction(Item source, boolean copy) { _source = source; _copy =
+	 * copy; }
+	 * 
+	 * public void actionPerformed(ActionEvent ae) { if (_copy) { Item copy =
+	 * _source.copy(); copy.setX(DisplayIO.getMouseX());
+	 * copy.setY(DisplayIO.getMouseY()); FrameMouseActions.pickup(copy); return; }
+	 * 
+	 * if (_source.getAction() != null) { _source.performActions(); } else {
+	 * FrameUtils.DisplayFrame(_source.getLink()); } } }
+	 */
+
+	public boolean isSaved() {
+		return _saved;
+	}
+
+	public void setSaved() {
+		_saved = true;
+		_change = false;
+	}
+
+	public static boolean rubberbandingLine() {
+		return FreeItems.size() == 2
+				&& (FreeItems.get(0) instanceof Line || FreeItems.get(1) instanceof Line);
+	}
+
+	public static boolean itemAttachedToCursor() {
+		return FreeItems.size() > 0;
+	}
+
+	public static boolean textItemAttachedToCursor() {
+		return itemAttachedToCursor() && FreeItems.get(0) instanceof Text;
+	}
+
+	public static Item getItemAttachedToCursor() {
+		if (itemAttachedToCursor())
+			return FreeItems.get(0);
+
+		return null;
+	}
+
+	/**
+	 * Tests if an item is a non title, non frame name, non special annotation
+	 * text item.
+	 * 
+	 * @param it
+	 *            the item to be tested
+	 * @return true if the item is a normal text item
+	 */
+	public boolean isNormalTextItem(Item it) {
+		if (it instanceof Text && it != getTitle() && it != _frameName
+				&& !((Text) it).isSpecialAnnotation()) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Moves the mouse to the end of the text item with a specified index.
+	 * 
+	 * @param index
+	 */
+	public boolean moveMouseToTextItem(int index) {
+		List<Item> items = getItems();
+		int itemsFound = 0;
+		for (int i = 0; i < items.size(); i++) {
+			Item it = items.get(i);
+			if (isNormalTextItem(it))
+				itemsFound++;
+			if (itemsFound > index) {
+				DisplayIO.setCursorPosition(((Text) it)
+						.getEndParagraphPosition().x, it.getY());
+				DisplayIO.resetCursorOffset();
+				FrameGraphics.Repaint();
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/*
+	 * public boolean moveMouseToNextTextItem(int index) { List<Item> items =
+	 * getItems(); int itemsFound = 0; for (int i = 0; i < items.size(); i++) {
+	 * Item it = items.get(i); if ( isNormalTextItem(it)) itemsFound++; if
+	 * (itemsFound > index) {
+	 * DisplayIO.setCursorPosition(((Text)it).getEndParagraphPosition().x,
+	 * it.getY()); DisplayIO.resetCursorOffset(); FrameGraphics.Repaint();
+	 * return true; } }
+	 * 
+	 * return false; }
+	 */
+
+	/**
+	 * Searches for an annotation item called start to be used as the default
+	 * cursor location when TDFC occurs.
+	 */
+	public boolean moveMouseToDefaultLocation() {
+		List<Item> items = getItems();
+
+		for (Item it : items) {
+			if (it instanceof Text) {
+				Text t = (Text) it;
+				if (t.getTextNoList().toLowerCase().startsWith("@start:")
+						|| t.getTextNoList().toLowerCase().equals("@start")) {
+					t.stripFirstWord();
+
+					if (t.getTextNoList().equals(""))
+						DisplayIO.getCurrentFrame().removeItem(t);
+					if (!Frame.itemAttachedToCursor()) {
+						DisplayIO.setCursorPosition(((Text) it)
+								.getEndParagraphPosition());
+						DisplayIO.resetCursorOffset();
+					}
+					FrameGraphics.Repaint();
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Gets the file name that actions should use to export files created by
+	 * running actions from this frame.
+	 * 
+	 * @return the fileName if the frame contains an '@file' tag. Returns the
+	 *         name of the frame if the tag isnt on the frame.
+	 */
+	public String getExportFileName() {
+		Text tag = ItemUtils.FindTag(getItems(), "@file:");
+		if (tag != null) {
+			String file = ItemUtils.StripTag(tag.getFirstLine(), "@file:");
+			file = file.trim();
+			if (file.length() > 0)
+				return file;
+		}
+
+		return _frameName.getTextNoList();
+	}
+
+	public void toggleBackgroundColor() {
+		setBackgroundColor(ColorUtils.getNextColor(_background,
+				Frame.COLOR_WHEEL));
+	}
+}
Index: /trunk/src/org/expeditee/gui/FrameCreator.java
===================================================================
--- /trunk/src/org/expeditee/gui/FrameCreator.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/FrameCreator.java	(revision 6)
@@ -0,0 +1,150 @@
+package org.expeditee.gui;
+
+import java.awt.Color;
+
+import org.expeditee.items.Item;
+import org.expeditee.items.Text;
+
+public class FrameCreator {
+
+	private String _name;
+	
+	private final static int START_Y = 40;
+
+	private final static int START_X = 20;
+
+	private Frame _current = null;
+
+	private int _lastX = START_X;
+
+	private int _maxX = 0;
+
+	private int _lastY =  START_Y;
+
+	private Text _Mnext = new Text(-1, "@Next");
+
+	private Text _Mprev = new Text(-1, "@Previous");
+
+	private Item _next;
+
+	private Item _prev;
+
+	public FrameCreator(String name, boolean recreate) {
+		int num = -1;
+		if (!recreate)
+			num = FrameIO.getLastNumber(name);
+
+		Frame toUse = null;
+
+		if (num == -1)
+			toUse = FrameIO.CreateFrameset(name, FrameIO.HELP_PATH);
+		else
+			toUse = FrameIO.CreateFrame(name, name, null);
+
+		_name = name;
+		_lastY += toUse.getTitle().getPosition().y;
+		_current = toUse;
+
+		// set positions of next\prev frame links
+		_Mnext.setPosition(FrameGraphics.getMaxSize().width - 100,
+				FrameGraphics.getMaxSize().height - 15);
+		_Mprev.setPosition(50, FrameGraphics.getMaxSize().height - 15);
+	}
+
+	/**
+	 * Creates the next frame in the frameset, with a previous button already
+	 * added and linked to the last frame. _current then gets updated to point
+	 * at the newly created Frame, and _lastY is reset
+	 */
+	public void createNextFrame() {
+		Frame newFrame = FrameIO.CreateFrame(_current.getFramesetName(),
+				_name, null);
+
+		// add link to previous frame
+		_prev = _Mprev.copy();
+		_prev.setID(newFrame.getNextItemID());
+		_prev.setLink(_current.getFrameName());
+		newFrame.addItem(_prev);
+
+		// add link to new frame
+		_next = _Mnext.copy();
+		_next.setID(_current.getNextItemID());
+		_next.setLink(newFrame.getFrameName());
+		_current.addItem(_next);
+		FrameIO.SaveFrame(_current, false);
+
+		// update\reset globals
+		_current = newFrame;
+		_lastY = START_Y + _current.getTitle().getPosition().y;;
+		_lastX = START_X;
+		_maxX = 0;
+	}
+
+	public void addItem(Item toAdd) {
+		// if we have reached the end of the Y axis, try moving over on the X
+		// axis
+		if (_lastY >= _Mprev.getY() - _Mprev.getBoundsHeight()) {
+			_lastX = _maxX + 10;
+			_lastY = START_Y;
+		}
+
+		// if there is no more room on the X axis, we have to start a new frame
+		if (toAdd.getBoundsWidth() + _lastX > FrameGraphics.getMaxSize().width) {
+			createNextFrame();
+		}
+
+		toAdd.setPosition(_lastX, _lastY);
+		toAdd.setOffset(0, 0);
+		toAdd.setID(_current.getNextItemID());
+
+		/*
+		 * List<Item> items = new ArrayList<Item>(4);
+		 * 
+		 * items.add(_current.getName()); items.add(_current.getTitle());
+		 * items.add(_prev); items.add(_next);
+		 * 
+		 * _current.getItems().removeAll(items);
+		 * 
+		 * //new system.agents.Format().process(_current);
+		 * 
+		 * //FrameUtils.Align(items, toAdd, true);
+		 */
+		_current.addItem(toAdd);
+		// _current.addAllItems(items);
+
+		FrameIO.SaveFrame(_current, false);
+
+		_lastY = toAdd.getY() + toAdd.getBoundsHeight();
+		_maxX = Math.max(toAdd.getX() + toAdd.getBoundsWidth(), _maxX);
+	}
+
+	public void addText(String toAdd) {
+		/*
+		 * Text text = _current.createNewText(); text.setText(toAdd);
+		 * 
+		 * addItem(text);
+		 */
+		addText(toAdd, null);
+	}
+
+	public void addText(String toAdd, Color c) {
+		Text text = _current.createNewText();
+		text.setText(toAdd);
+		text.setColor(c);
+
+		addItem(text);
+	}
+
+	/**
+	 * Returns the Frame name of the current (latest) frame in the message
+	 * frameset.
+	 * 
+	 * @return The Frame currently being used in the message frameset
+	 */
+	public String getCurrent() {
+		if (_current == null)
+			return null;
+
+		return _current.getFrameName();
+	}
+}
Index: /trunk/src/org/expeditee/gui/FrameGraphics.java
===================================================================
--- /trunk/src/org/expeditee/gui/FrameGraphics.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/FrameGraphics.java	(revision 6)
@@ -0,0 +1,715 @@
+package org.expeditee.gui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsEnvironment;
+import java.awt.RenderingHints;
+import java.awt.image.VolatileImage;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.actions.Misc;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Picture;
+import org.expeditee.items.Text;
+
+public class FrameGraphics {
+	public static final int MESSAGE_BUFFER_HEIGHT = 100;
+
+	private static final int MESSAGE_LINK_Y_OFFSET = 100;
+
+	private static final int MESSAGE_LINK_X = 50;
+
+	// the graphics used to paint with
+	private static Graphics2D _DisplayGraphics;
+
+	// the maximum size that can be used to paint on
+	private static Dimension _MaxSize;
+
+	// messages shown in the message window
+	public static Text[] Messages = new Text[3];
+
+	// buffer of the message window
+	private static VolatileImage _MessageBuffer = null;
+
+	// font used for the messages
+	private static Font _MessageFont = Font.decode("Serif-Plain-16");
+
+	// the number of messages currently shown (used for scrolling up)
+	private static int _MessageCount = 1;
+
+	// modes
+	public static final int MODE_NORMAL = 0;
+
+	public static final int MODE_AUDIENCE = 1;
+
+	public static final int MODE_XRAY = 2;
+
+	private static int _Mode = MODE_NORMAL;
+
+	// if true, error messages are not shown to the user
+	private static boolean _SupressErrors = false;
+
+	// The link to the message frameset
+	public static Text MessageLink = new Text(-2, "Messages");
+
+	// Date\time formatter for timestamping the messages
+	/*
+	 * private static SimpleDateFormat _formatter = new SimpleDateFormat( "
+	 * [ddMMMyyyy:HHmm]");
+	 */
+
+	// creator for creating the message frames
+	private static FrameCreator _creator;
+
+	/**
+	 * Returns the Graphics2D object currently being used to draw with, this may
+	 * be null if no Graphics2D has been assigned yet.
+	 * 
+	 * @return the Graphics2D object being used to draw, or null.
+	 */
+	public static Graphics2D getGraphics() {
+		return _DisplayGraphics;
+	}
+
+	/**
+	 * If Audience Mode is on this method will toggle it to be off, or
+	 * vice-versa. This results in the Frame being re-parsed and repainted.
+	 */
+	public static void ToggleAudienceMode() {
+		if (_Mode == MODE_AUDIENCE)
+			_Mode = MODE_NORMAL;
+		else
+			_Mode = MODE_AUDIENCE;
+
+		FrameUtils.Parse(DisplayIO.getCurrentFrame());
+		DisplayIO.UpdateTitle();
+		Repaint();
+	}
+
+	/**
+	 * If X-Ray Mode is on this method will toggle it to be off, or vice-versa.
+	 * This results in the Frame being re-parsed and repainted.
+	 */
+	public static void ToggleXRayMode() {
+		if (_Mode == MODE_XRAY)
+			_Mode = MODE_NORMAL;
+		else
+			_Mode = MODE_XRAY;
+
+		FrameUtils.Parse(DisplayIO.getCurrentFrame());
+		Repaint();
+	}
+
+	/**
+	 * @return True if Audience Mode is currently on, False otherwise.
+	 */
+	public static boolean isAudienceMode() {
+		return _Mode == MODE_AUDIENCE;
+	}
+
+	/**
+	 * @return True if X-Ray Mode is currently on, False otherwise.
+	 */
+	public static boolean isXRayMode() {
+		return _Mode == MODE_XRAY;
+	}
+
+	public static void setMaxSize(Dimension max) {
+		if (_MaxSize == null)
+			_MaxSize = max;
+
+		// Mike assumes this is the height of the Message window?
+		_MaxSize.setSize(max.width, max.height - DisplayIO.getOffset()
+				- MESSAGE_BUFFER_HEIGHT);
+		if (DisplayIO.getCurrentFrame() != null) {
+			DisplayIO.getCurrentFrame().setBuffer(null);
+			DisplayIO.getCurrentFrame().setMaxSize(max);
+		}
+
+		_MessageBuffer = null;
+
+		for (int i = 0; i < Messages.length; i++) {
+			if (Messages[i] != null) {
+				Messages[i].setOffset(0, -_MaxSize.height);
+				Messages[i].setMaxSize(_MaxSize);
+			}
+		}
+
+		MessageLink.setOffset(0, -_MaxSize.height);
+		MessageLink.setMaxSize(_MaxSize);
+		MessageLink.setPosition(_MaxSize.width - MESSAGE_LINK_Y_OFFSET,
+				MESSAGE_LINK_X);
+
+		Repaint();
+	}
+
+	public static Dimension getMaxSize() {
+		return _MaxSize;
+	}
+
+	public static Dimension getMaxFrameSize() {
+		if (DisplayIO.isTwinFramesOn()) {
+			return new Dimension((_MaxSize.width / 2), _MaxSize.height);
+		} else
+			return _MaxSize;
+	}
+
+	/**
+	 * Sets the Graphics2D object that should be used for all painting tasks.
+	 * Note: Actual painting is done by using g.create() to create temporary
+	 * instances that are then disposed of using g.dispose().
+	 * 
+	 * @param g
+	 *            The Graphics2D object to use for all painting
+	 */
+	public static void setDisplayGraphics(Graphics2D g) {
+		_DisplayGraphics = g;
+	}
+
+	/*
+	 * Displays the given Item on the screen
+	 */
+	private static void PaintItem(Graphics2D g, Item i) {
+		if (i == null || g == null)
+			return;
+
+		// do not paint annotation items in audience mode
+		if (g != null
+				&& (!isAudienceMode()
+						|| (isAudienceMode() && !i.isAnnotation()) || i == FrameUtils.LastEdited))
+			// do not paint the name of overlay frames
+			if (i.getParent() == null
+					|| i.getParent() == DisplayIO.getCurrentFrame()
+					|| i.getParent() == DisplayIO.getOppositeFrame()
+					|| i != i.getParent().getName()) {
+				Graphics2D tg = (Graphics2D) g.create();
+				i.paint(tg);
+				tg.dispose();
+
+			}
+	}
+
+	private static void AddAllOverlayItems(List<Item> items, Frame overlay,
+			List<Frame> seenOverlays) {
+		if (seenOverlays.contains(overlay))
+			return;
+
+		seenOverlays.add(overlay);
+
+		for (Overlay o : overlay.getOverlays())
+			AddAllOverlayItems(items, o.Frame, seenOverlays);
+
+		items.addAll(overlay.getItems());
+	}
+
+	private static VolatileImage Paint(Frame toPaint) {
+		return Paint(toPaint, true);
+	}
+
+	private static VolatileImage Paint(Frame toPaint, boolean paintOverlay) {
+		if (toPaint == null)
+			return null;
+
+		// the buffer is not valid, so it must be recreated
+		if (!toPaint.isBufferValid()) {
+			VolatileImage buffer = toPaint.getBuffer();
+			if (buffer == null) {
+				GraphicsEnvironment ge = GraphicsEnvironment
+						.getLocalGraphicsEnvironment();
+				buffer = ge.getDefaultScreenDevice().getDefaultConfiguration()
+						.createCompatibleVolatileImage(_MaxSize.width,
+								_MaxSize.height);
+				toPaint.setBuffer(buffer);
+			}
+
+			Graphics2D bg = (Graphics2D) buffer.getGraphics();
+			// Text items should automatically be Anti-Aliased now
+			/*
+			 * bg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+			 * RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+			 */
+
+			// Nicer looking lines, but may be too jerky while
+			// rubber-banding on older machines
+			if (UserSettings.AntiAlias)
+				((Graphics2D) bg).setRenderingHint(
+						RenderingHints.KEY_ANTIALIASING,
+						RenderingHints.VALUE_ANTIALIAS_ON);
+
+			bg.setColor(toPaint.getPaintBackgroundColor());
+			bg.fillRect(0, 0, _MaxSize.width, _MaxSize.height);
+
+			List<Item> paintItems = new LinkedList<Item>();
+			List<Frame> seenOverlays = new LinkedList<Frame>();
+			if (paintOverlay)
+				AddAllOverlayItems(paintItems, toPaint, seenOverlays);
+			else {
+				paintItems.addAll(toPaint.getItems());
+				paintItems.remove(toPaint.getFrameNameItem());
+			}
+			PaintPictures(bg, paintItems);
+
+			if (toPaint == DisplayIO.getCurrentFrame())
+				PaintPictures(bg, Frame.FreeItems);
+
+			PaintNonLinesNonPicture(bg, paintItems);
+			PaintLines(bg, paintItems);
+
+			if (toPaint == DisplayIO.getCurrentFrame())
+				PaintNonLinesNonPicture(bg, Frame.FreeItems);
+
+			// toPaint.setBufferValid(true);
+
+			if (DisplayIO.isTwinFramesOn()) {
+				List<Item> lines = new LinkedList<Item>();
+				for (Item i : Frame.FreeItems) {
+					if (i instanceof Line) {
+						Line line = (Line) i;
+
+						if (toPaint != DisplayIO.getCurrentFrame()) {
+							if (line.getEndItem().isFloating()
+									^ line.getStartItem().isFloating()) {
+								lines.add(TransposeLine(line,
+										line.getEndItem(), toPaint, DisplayIO
+												.getRealMouseX(), -DisplayIO
+												.getMiddle()));
+								lines.add(TransposeLine(line, line
+										.getStartItem(), toPaint, DisplayIO
+										.getRealMouseX(), -DisplayIO
+										.getMiddle()));
+							}
+						} else {
+							if (line.getEndItem().isFloating()
+									^ line.getStartItem().isFloating()) {
+								Line l = TransposeLine(line, line.getEndItem(),
+										toPaint, 0, 0);
+								if (l == null)
+									l = TransposeLine(line,
+											line.getStartItem(), toPaint, 0, 0);
+								if (l == null)
+									l = line;
+								lines.add(l);
+							} else
+								lines.add(line);
+						}
+					}
+				}
+				PaintLines(bg, lines);
+			} else {
+				// PaintPictures(bg, Frame.FreeItems);
+				// PaintNonLinesNonPicture(bg, Frame.FreeItems);
+				PaintLines(bg, Frame.FreeItems);
+			}
+
+			bg.dispose();
+		}
+
+		return toPaint.getBuffer();
+	}
+
+	// creates a new line so that lines are shown correctly when spanning
+	// across frames in TwinFrames mode
+	private static Line TransposeLine(Line line, Item d, Frame toPaint,
+			int base, int adj) {
+		Line nl = null;
+
+		if (toPaint != DisplayIO.getCurrentFrame() && d.getParent() == null
+				&& line.getOppositeEnd(d).getParent() == toPaint) {
+			nl = line.copy();
+			if (d == line.getStartItem())
+				d = nl.getStartItem();
+			else
+				d = nl.getEndItem();
+
+			if (DisplayIO.FrameOnSide(toPaint) == 0)
+				d.setX(base);
+			else
+				d.setX(base + adj);
+
+		} else if (toPaint == DisplayIO.getCurrentFrame()
+				&& d.getParent() == null
+				&& line.getOppositeEnd(d).getParent() != toPaint) {
+			nl = line.copy();
+
+			if (d == line.getStartItem())
+				d = nl.getEndItem();
+			else
+				d = nl.getStartItem();
+
+			if (DisplayIO.FrameOnSide(toPaint) == 1)
+				d.setX(d.getX() - DisplayIO.getMiddle());
+			else
+				d.setX(d.getX() + DisplayIO.getMiddle());
+		}
+
+		return nl;
+	}
+
+	private static void Paint(VolatileImage left, VolatileImage right,
+			Color background) {
+		if (_MessageBuffer == null) {
+			GraphicsEnvironment ge = GraphicsEnvironment
+					.getLocalGraphicsEnvironment();
+			_MessageBuffer = ge.getDefaultScreenDevice()
+					.getDefaultConfiguration().createCompatibleVolatileImage(
+							_MaxSize.width, MESSAGE_BUFFER_HEIGHT);
+		}
+
+		paintMessage(_MessageBuffer.createGraphics(), background);
+		Graphics g = _DisplayGraphics.create();
+
+		// if TwinFrames mode is on, then clipping etc has to be set
+		if (DisplayIO.isTwinFramesOn()) {
+			// draw the two lines down the middle of the window
+			if (left != null)
+				left.getGraphics().drawLine(DisplayIO.getMiddle() - 2, 0,
+						DisplayIO.getMiddle() - 2, _MaxSize.height);
+
+			if (right != null)
+				right.getGraphics().drawLine(0, 0, 0, _MaxSize.height);
+
+			// set the clipping area
+			((Graphics2D) g).setClip(0, 0, DisplayIO.getMiddle() - 1,
+					_MaxSize.height + DisplayIO.getOffset());
+			g.drawImage(left, 0, DisplayIO.getOffset(),
+					DisplayIO.DEFAULT_BACKGROUND, null);
+			((Graphics2D) g).setClip(null);
+			g.drawImage(right, DisplayIO.getMiddle() + 1,
+					DisplayIO.getOffset(), DisplayIO.DEFAULT_BACKGROUND, null);
+
+			// otherwise, just draw whichever side is active
+		} else {
+			if (DisplayIO.getCurrentSide() == 0)
+				g.drawImage(left, 0, DisplayIO.getOffset(),
+						DisplayIO.DEFAULT_BACKGROUND, null);
+			else
+				g.drawImage(right, 0, DisplayIO.getOffset(),
+						DisplayIO.DEFAULT_BACKGROUND, null);
+		}
+		// draw the message area
+		g.drawImage(_MessageBuffer, 0, _MaxSize.height + DisplayIO.getOffset(),
+				null);
+		g.dispose();
+	}
+
+	private static void paintMessage(Graphics2D g, Color background) {
+		((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+		g.setColor(background);
+		g.fillRect(0, 0, _MaxSize.width, MESSAGE_BUFFER_HEIGHT);
+		g.setFont(_MessageFont);
+		g.setColor(Color.BLACK);
+		g.drawLine(0, 0, _MaxSize.width, 0);
+		for (Text t : Messages)
+			PaintItem(g, t);
+		if (MessageLink.getLink() != null)
+			PaintItem(g, MessageLink);
+		g.dispose();
+	}
+
+	public static void Clear() {
+		Graphics g = _DisplayGraphics.create();
+		g.setColor(Color.WHITE);
+		g.fillRect(0, 0, _MaxSize.width, _MaxSize.height);
+		g.dispose();
+	}
+
+	/**
+	 * Called to refresh the display screen.
+	 * 
+	 */
+	public static void Repaint() {
+		if (_DisplayGraphics == null)
+			return;
+
+		if (UserSettings.Threading) {
+			if (painter == null) {
+				painter = new FrameGraphics().new Repainter();
+
+				painter.setDaemon(true);
+				painter.setPriority(Thread.MIN_PRIORITY);
+
+				painter.start();
+			} else
+				painter.run();
+		} else {
+			Frame[] toPaint = DisplayIO.getFrames();
+
+			if (toPaint != null) {
+				VolatileImage left = Paint(toPaint[0]);
+				VolatileImage right = Paint(toPaint[1]);
+				Paint(left, right, DisplayIO.DEFAULT_BACKGROUND);
+			}
+		}
+	}
+
+	private static Repainter painter = null;
+
+	private static void PaintNonLinesNonPicture(Graphics2D g, List<Item> toPaint) {
+		for (Item i : toPaint)
+			if (!(i instanceof Line) && !(i instanceof Picture))
+				PaintItem(g, i);
+	}
+
+	private static void PaintLines(Graphics2D g, List<Item> toPaint) {
+		for (Item i : toPaint)
+			if (i instanceof Line)
+				PaintItem(g, i);
+	}
+
+	private static void PaintPictures(Graphics2D g, List<Item> toPaint) {
+		for (Item i : toPaint) {
+			if (i instanceof Picture)
+				PaintItem(g, i);
+			else if (i instanceof Dot)
+				((Dot) i).paintFill(g);
+		}
+	}
+
+	/**
+	 * Highlights an item on the screen Note: All graphics are handled by the
+	 * Item itself.
+	 * 
+	 * @param i
+	 *            The Item to highlight.
+	 * @param val
+	 *            True if the highlighting is being shown, false if it is being
+	 *            erased.
+	 */
+	public static void Highlight(Item i, boolean val, boolean changeMouse) {
+		if ((i instanceof Line/* Dot */)) {
+			List<Item> connected = i.getAllConnected();
+
+			for (Item conn : connected)
+				conn.showHighlight(val);
+		} else {
+			i.showHighlight(val);
+		}
+
+		Repaint();
+	}
+
+	public static void OverwriteMessage(String message) {
+		for (int ind = Messages.length - 1; ind >= 0; ind--) {
+			if (Messages[ind] != null) {
+				Messages[ind].setText(message);
+				Repaint();
+				return;
+			}
+		}
+
+		// if we have not returned, then there are no messages yet
+		DisplayMessage(message);
+	}
+
+	/**
+	 * Displays the given message in the message area of the Frame, any previous
+	 * message is cleared from the screen.
+	 * 
+	 * @param message
+	 *            The message to display to the user in the message area
+	 */
+	public static void DisplayMessage(String message) {
+		DisplayMessage(message, Color.BLACK);
+	}
+
+	public static void DisplayMessage(String message, Color textColor) {
+		displayMessage(message, null, textColor);
+		Misc.Beep();
+	}
+
+	public static void DisplayMessage(Text message) {
+		displayMessage(message.getFirstLine(), message.getLink(), message
+				.getColor());
+		Misc.Beep();
+	}
+
+	public static void DisplayMessageAlways(String message) {
+		_lastMessage = null;
+		displayMessage(message, null, Color.BLACK);
+		Misc.Beep();
+	}
+
+	public static void WarningMessage(String message) {
+		displayMessage(message, null, Color.MAGENTA);
+		Misc.Beep();
+	}
+
+	private static String _lastMessage = null;
+
+	private static void displayMessage(String message, String link, Color color) {
+		// add timestamp to message
+		if (message.equals(_lastMessage))
+			return;
+		_lastMessage = message;
+
+		// message += _formatter.format(Calendar.getInstance().getTime());
+
+		// if the creator needs to be initialised (happens on first message)
+		if (_creator == null) {
+			_creator = new FrameCreator("Messages", true);
+
+			// set up 'Messages' link on the right hand side
+			MessageLink.setPosition(_MaxSize.width - MESSAGE_LINK_Y_OFFSET,
+					MESSAGE_LINK_X);
+			MessageLink.setOffset(0, -_MaxSize.height);
+		}
+
+		// if the message slots have not all been used yet
+		if (_MessageCount < 4) {
+			int pos = 15;
+			// find the next empty slot, and create the new message
+			for (int i = 0; i < Messages.length; i++) {
+				if (Messages[i] == null) {
+					Messages[i] = new Text(-1, _MessageCount++ + ": " + message);
+					Messages[i].setPosition(20, pos);
+					Messages[i].setOffset(0, -_MaxSize.height);
+					Messages[i].setMaxSize(_MaxSize);
+					Messages[i].setColor(color);
+					Messages[i].setLink(link);
+
+					_creator.addItem(Messages[i].copy());
+					MessageLink.setLink(_creator.getCurrent());
+					Repaint();
+					return;
+				}
+
+				pos += 25;
+			}
+		}
+
+		// if we have not returned then all message slots are used
+		for (int i = 0; i < Messages.length - 1; i++) {
+			Messages[i].setText(Messages[i + 1].getFirstLine());
+			Messages[i].setColor(Messages[i + 1].getColor());
+			Messages[i].setLink(Messages[i + 1].getLink());
+		}
+
+		// show the new message
+		Text last = Messages[Messages.length - 1];
+		last.setColor(color);
+		last.setText(_MessageCount++ + ": " + message);
+		last.setLink(link);
+
+		_creator.addItem(last.copy());
+		// update the link to the latest message frame
+		MessageLink.setLink(_creator.getCurrent());
+		Repaint();
+	}
+
+	/**
+	 * Checks if the error message ends with a frame name after the
+	 * frameNameSeparator symbol
+	 * 
+	 * @param message
+	 *            the message to be displayed
+	 */
+	public static void LinkedErrorMessage(String message) {
+		if (_SupressErrors)
+			return;
+		Misc.Beep();
+		String[] tokens = message.split(Text.FRAME_NAME_SEPARATOR);
+		String link = null;
+		if (tokens.length > 1)
+			link = tokens[tokens.length - 1];
+		displayMessage(message, link, Color.RED);
+	}
+
+	public static void ErrorMessage(String message) {
+		if (_SupressErrors)
+			return;
+		Misc.Beep();
+		displayMessage(message, null, Color.RED);
+
+		// Michael is confused about why this code is gettting into infinite
+		// loop
+
+		/*
+		 * Exception e = new Exception();
+		 * 
+		 * try{ throw e; }catch(Exception ex){ StackTraceElement[] trace =
+		 * ex.getStackTrace(); message += " (" + trace[1].getFileName() + ":" +
+		 * trace[1].getLineNumber() + ")";
+		 * 
+		 * String source = trace[1].getClassName(); source = "src.java." +
+		 * source; source = source.replace(".", File.separator); source +=
+		 * ".java";
+		 * 
+		 * try{ BufferedReader reader = new BufferedReader(new
+		 * FileReader(source)); FrameCreator creator = new
+		 * FrameCreator("Messages", false);
+		 * 
+		 * int min = trace[1].getLineNumber() - 10; int max =
+		 * trace[1].getLineNumber() + 3;
+		 * 
+		 * int line = 0; while(reader.ready()){ if(line > min && line < max)
+		 * if(line == trace[1].getLineNumber() - 1)
+		 * creator.addText(reader.readLine(), Color.RED); else
+		 * creator.addText(reader.readLine()); else if(line > max) break; else
+		 * reader.readLine();
+		 * 
+		 * line++; }
+		 * 
+		 * reader.close();
+		 * 
+		 * displayMessage(message, creator.getCurrent(), Color.RED);
+		 * }catch(Exception ioe){ ioe.printStackTrace(); } }
+		 */
+	}
+
+	private class Repainter extends Thread {
+		public boolean isPainting = false;
+
+		public void run() {
+			isPainting = true;
+
+			Frame[] toPaint = DisplayIO.getFrames();
+
+			if (toPaint != null) {
+				VolatileImage left = Paint(toPaint[0]);
+				VolatileImage right = Paint(toPaint[1]);
+				Paint(left, right, DisplayIO.DEFAULT_BACKGROUND);
+			}
+
+			isPainting = false;
+		}
+	}
+
+	/**
+	 * Invalidates the buffered image of the current Frame and forces it to be
+	 * repainted on to the screen.
+	 */
+	public static void ForceRepaint() {
+		Frame current = DisplayIO.getCurrentFrame();
+
+		if (current == null)
+			return;
+
+		Repaint();
+	}
+
+	/**
+	 * Repaints the buffer of the given Frame.
+	 * 
+	 * @param toUpdate
+	 *            The Frame whose buffer is to be repainted.
+	 */
+
+	public static void UpdateBuffer(Frame toUpdate, boolean paintOverlays) {
+		if (toUpdate == null)
+			return;
+
+		VolatileImage vi = Paint(toUpdate, paintOverlays);
+		toUpdate.setBuffer(vi);
+	}
+
+	public static void SupressErrors(boolean val) {
+		_SupressErrors = val;
+	}
+}
Index: /trunk/src/org/expeditee/gui/FrameIO.java
===================================================================
--- /trunk/src/org/expeditee/gui/FrameIO.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/FrameIO.java	(revision 6)
@@ -0,0 +1,898 @@
+package org.expeditee.gui;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.expeditee.io.Conversion;
+import org.expeditee.io.ExpReader;
+import org.expeditee.io.ExpWriter;
+import org.expeditee.io.FrameReader;
+import org.expeditee.io.FrameWriter;
+import org.expeditee.io.KMSReader;
+import org.expeditee.io.KMSWriter;
+import org.expeditee.io.Logger;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Text;
+import org.expeditee.stats.SessionStats;
+
+/**
+ * This class provides static methods for all saving and loading of Frames
+ * to\from disk. This class also handles any caching of previously loaded
+ * Frames.
+ * 
+ * @author jdm18
+ * 
+ */
+public class FrameIO {
+
+	public static void changeParentFolder(String newFolder) {
+		PARENT_FOLDER = newFolder;
+		FRAME_PATH = PARENT_FOLDER + "framesets" + File.separator;
+		IMAGES_PATH = PARENT_FOLDER + "images" + File.separator;
+		HELP_PATH = PARENT_FOLDER + "help" + File.separator;
+		PROFILE_PATH = PARENT_FOLDER + "profiles" + File.separator;
+		EXPORTS_DIR = PARENT_FOLDER + "exports" + File.separator;
+		STATISTICS_DIR = PARENT_FOLDER + "statistics" + File.separator;
+		LOGS_DIR = PARENT_FOLDER + "logs" + File.separator;
+	}
+
+	/**
+	 * The default location for storing the framesets. Each frameset has its own
+	 * subdirectory in this directory.
+	 */
+
+	public static String PARENT_FOLDER;
+
+	public static String FRAME_PATH;
+
+	public static String IMAGES_PATH;
+
+	public static String HELP_PATH;
+
+	public static String PROFILE_PATH;
+
+	public static String EXPORTS_DIR;
+
+	public static String STATISTICS_DIR;
+
+	public static String LOGS_DIR;
+
+	private static final String INF_FILENAME = "frame.inf";
+
+	public static final String ILLEGAL_CHARS = ";:\\/?";
+
+	public static final int MAX_NAME_LENGTH = 64;
+
+	public static final int MAX_CACHE = 20;
+
+	private static HashMap<String, Frame> _Cache = new HashMap<String, Frame>();
+
+	// private static HashMap<String, String> _FramesetNameCache = new
+	// HashMap<String, String>();
+
+	private static boolean ENABLE_CACHE = true;
+
+	private static boolean _UseCache = true;
+
+	private static boolean _SuspendedCache = false;
+
+	// All methods are static, this should not be instantiated
+	private FrameIO() {
+	}
+
+	public static boolean isCacheOn() {
+		return _UseCache && ENABLE_CACHE;
+	}
+
+	public static void Precache(String framename) {
+		// if the cache is turned off, do nothing
+		if (!isCacheOn())
+			return;
+
+		// if the frame is already in the cache, do nothing
+		if (_Cache.containsKey(framename.toLowerCase()))
+			return;
+
+		// otherwise, load the frame and put it in the cache
+		Logger.Log(Logger.SYSTEM, Logger.LOAD, "Precaching " + framename + ".");
+
+		// do not display errors encountered to the user
+		// (they will be shown at load time)
+		FrameGraphics.SupressErrors(true);
+		// loading automatically caches the frame is caching is turned on
+		LoadUnknownPath(framename);
+		FrameGraphics.SupressErrors(false);
+	}
+
+	/**
+	 * Checks if a string is a representation of a positive integer.
+	 * 
+	 * @param s
+	 * @return true if s is a positive integer
+	 */
+	public static boolean isPositiveInteger(String s) {
+		if (s == null || s.length() == 0)
+			return false;
+
+		for (int i = 0; i < s.length(); i++) {
+			if (!Character.isDigit(s.charAt(i)))
+				return false;
+		}
+		return true;
+	}
+
+	public static Frame LoadFrame(String frameName) {
+		// If the frame name is a positive integer add the frameset name
+		if (isPositiveInteger(frameName)){
+			assert(false);
+			frameName = DisplayIO.getCurrentFrame().getFramesetNameAdjusted()
+					+ frameName;
+		}
+
+		// first try reading from cache
+		if (isCacheOn() && _Cache.containsKey(frameName.toLowerCase())) {
+			Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName
+					+ " from cache.");
+			return _Cache.get(frameName.toLowerCase());
+		}
+
+		Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName
+				+ " from disk.");
+		return LoadUnknownPath(frameName);
+	}
+
+	private static Frame LoadUnknownPath(String framename) {
+		Frame loaded = null;
+
+		for (String path : UserSettings.FrameDirs) {
+			loaded = LoadFrame(path, framename);
+			if (loaded != null) {
+				FrameUtils.Parse(loaded, true);
+				break;
+			}
+		}
+
+		return loaded;
+	}
+
+	/**
+	 * Gets the full path and file name of the frame.
+	 * 
+	 * @param path-
+	 *            the directory in which to look for the frameset containing the
+	 *            frame.
+	 * @param frameName-
+	 *            the name of the frame for which the path is being requested.
+	 * @return null if the frame can not be located.
+	 */
+	public static String getFrameFullPathName(String path, String frameName) {
+		String source = path + Conversion.getFrameset(frameName)
+				+ File.separator;
+
+		File tester = new File(source);
+		if (!tester.exists())
+			return null;
+
+		// check for the new file name format
+		String fullPath = source + Conversion.getFrameNumber(frameName)
+				+ ExpReader.EXTENTION;
+		tester = new File(fullPath);
+
+		if (tester.exists())
+			return fullPath;
+
+		// check for oldfile name format
+		fullPath = source + Conversion.getFrameset(frameName) + "."
+				+ Conversion.getFrameNumber(frameName);
+		tester = new File(fullPath);
+
+		if (tester.exists())
+			return fullPath;
+
+		return null;
+	}
+
+	public static boolean DoesFrameExist(String frameName) {
+		for (String path : UserSettings.FrameDirs) {
+			if (getFrameFullPathName(path, frameName) != null)
+				return true;
+		}
+		return false;
+	}
+
+	private static Frame LoadFrame(String path, String frameName) {
+		String fullPath = getFrameFullPathName(path, frameName);
+		if (fullPath == null)
+			return null;
+
+		try {
+			FrameReader reader;
+
+			if (fullPath.endsWith(ExpReader.EXTENTION)) {
+				reader = new ExpReader(frameName);
+			} else {
+				reader = new KMSReader();
+			}
+			Frame frame = reader.readFrame(fullPath);
+
+			if (frame == null) {
+				FrameGraphics.ErrorMessage("Error: " + frameName
+						+ " could not be successfully loaded.");
+				return null;
+			}
+
+			frame.path = path;
+
+			// do not put 0 frames or virtual frames into the cache
+			if (_Cache.size() > MAX_CACHE)
+				_Cache.clear();
+
+			if (frame.getFrameNumber() > 0 && isCacheOn())
+				_Cache.put(frameName.toLowerCase(), frame);
+
+			return frame;
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+			Logger.Log(ioe);
+		} catch (Exception e) {
+			e.printStackTrace();
+			Logger.Log(e);
+			FrameGraphics.ErrorMessage("Error: " + frameName
+					+ " could not be successfully loaded.");
+		}
+
+		return null;
+	}
+
+	public static void Reload() {
+		// disable cache
+		boolean cache = _UseCache;
+
+		_UseCache = false;
+		Frame fresh = FrameIO.LoadFrame(DisplayIO.getCurrentFrame()
+				.getFrameName());
+		_UseCache = cache;
+
+		if (_Cache.containsKey(fresh.getFrameName().toLowerCase()))
+			_Cache.put(fresh.getFrameName().toLowerCase(), fresh);
+
+		DisplayIO.setCurrentFrame(fresh);
+	}
+
+	public static Frame LoadPrevious(Frame current) {
+		// the current name and number
+		String name = current.getFramesetNameAdjusted();
+		int num = current.getFrameNumber() - 1;
+
+		// loop until a frame that exists is found
+		for (; num >= 0; num--) {
+			Frame f = LoadFrame(name + num);
+			if (f != null)
+				return f;
+		}
+
+		// if we did not find another Frame then this one must be the last one
+		// in the frameset
+		FrameGraphics.DisplayMessage("This is the first frame in the frameset");
+		return null;
+	}
+
+	/**
+	 * Returns the next Frame in the current Frameset (The Frame with the next
+	 * highest Frame number) If the current Frame is the last one in the
+	 * Frameset, or an error occurs then null is returned.
+	 * 
+	 * @return The Frame after this one in the current frameset, or null
+	 */
+	public static Frame LoadNext(Frame current) {
+
+		// the current name and number
+		int num = current.getFrameNumber() + 1;
+		int max = num + 1;
+		String name = current.getFramesetNameAdjusted();
+
+		// read the maximum from the INF file
+		try {
+			max = ReadINF(current.path, current.getFramesetName());
+		} catch (IOException ioe) {
+			FrameGraphics.ErrorMessage("Error loading INF file for frameset '"
+					+ name + "'");
+			return null;
+		}
+
+		// loop until a frame that exists is found
+		for (; num <= max; num++) {
+			Frame f = LoadFrame(name + num);
+			if (f != null)
+				return f;
+		}
+
+		// if we did not find another Frame then this one must be the last one
+		// in the frameset
+		FrameGraphics.DisplayMessage("This is the last frame in the frameset");
+		return null;
+	}
+
+	public static Frame LoadLast(String framesetName, String path) {
+		// read the maximum from the INF file
+		int max;
+		try {
+			max = ReadINF(path, framesetName);
+		} catch (IOException ioe) {
+			FrameGraphics.ErrorMessage("Error loading INF file for frameset '"
+					+ framesetName + "'");
+			return null;
+		}
+
+		String adjustedName = Frame.GetFramesetNameAdjusted(framesetName);
+
+		// loop backwards until a frame that exists is found
+		for (int num = max; num > 0; num--) {
+			Frame f = LoadFrame(adjustedName + num);
+			if (f != null)
+				return f;
+		}
+
+		// if we did not find another Frame then this one must be the last one
+		// in the frameset
+		FrameGraphics.DisplayMessage("This is the last frame in the frameset");
+		return null;
+	}
+
+	public static Frame LoadLast() {
+		Frame current = DisplayIO.getCurrentFrame();
+		return LoadLast(current.getFramesetName(), current.path);
+	}
+
+	public static Frame LoadNext() {
+		return LoadNext(DisplayIO.getCurrentFrame());
+	}
+
+	public static Frame LoadPrevious() {
+		return LoadPrevious(DisplayIO.getCurrentFrame());
+	}
+
+	/**
+	 * Deletes the given Frame on disk and removes the cached Frame if there is
+	 * one. Also adds the deleted frame into the deletedFrames frameset.
+	 * 
+	 * @param toDelete
+	 *            The Frame to be deleted
+	 * @return The result of File.delete()
+	 */
+	public static boolean DeleteFrame(Frame toDelete) throws IOException {
+		SaveFrame(toDelete);
+
+		// get the last used frame in the destination frameset
+		final String DELETED_FRAMES = "DeletedFrames";
+		int lastNumber = FrameIO.getLastNumber(DELETED_FRAMES);
+		String framePath;
+		if (lastNumber < 0) {
+			// create the new frameset
+			Frame one = FrameIO.CreateFrameset(DELETED_FRAMES, toDelete.path);
+			framePath = one.path;
+			lastNumber = 0;
+		} else {
+			Frame zero = FrameIO.LoadFrame(DELETED_FRAMES + "0");
+			framePath = zero.path;
+		}
+
+		// get the fill path to determine which file version it is
+		String source = getFrameFullPathName(toDelete.path, toDelete
+				.getFrameName());
+
+		String oldFrameName = toDelete.getFrameName().toLowerCase();
+		// Now save the frame in the new location
+		toDelete.setFrameset(DELETED_FRAMES);
+		toDelete.setFrameNumber(lastNumber + 1);
+		toDelete.path = framePath;
+		FrameIO.SaveFrame(toDelete);
+
+		if (_Cache.containsKey(oldFrameName))
+			_Cache.remove(oldFrameName);
+
+		File del = new File(source);
+
+		java.io.FileInputStream ff = new java.io.FileInputStream(del);
+		ff.close();
+
+		return del.delete();
+	}
+
+	/**
+	 * Creates a new Frame in the given frameset and assigns it the given Title,
+	 * which can be null. The newly created Frame is a copy of the frameset's .0
+	 * file with the number updated based on the last recorded Frame name in the
+	 * frameset's INF file.
+	 * 
+	 * @param frameset
+	 *            The frameset to create the new Frame in
+	 * @param frameTitle
+	 *            The title to assign to the newly created Frame (can be NULL).
+	 * @return The newly created Frame.
+	 */
+	public static Frame CreateFrame(String frameset, String frameTitle,
+			String templateFrame) {
+
+		int next = -1;
+
+		// disable caching of 0 frames
+		SuspendCache();
+
+		String adjustFramesetName = Frame.GetFramesetNameAdjusted(frameset);
+		Frame template = null;
+		if (templateFrame == null)
+			// load in frame.0
+			template = LoadFrame(adjustFramesetName + "0");
+		else
+			template = LoadFrame(templateFrame);
+
+		ResumeCache();
+
+		// read the next number from the INF file
+		try {
+			next = ReadINF(template.path, frameset);
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+			Logger.Log(ioe);
+			return null;
+		}
+
+		// set the number and title of the new frame
+		template.setFrameNumber(++next);
+		template.setTitle(frameTitle);
+
+		Logger.Log(Logger.SYSTEM, Logger.TDFC, "Creating new frame: "
+				+ template.getFrameName() + " from TDFC");
+
+		// update INF file
+		try {
+			WriteINF(template.path, frameset, adjustFramesetName + next);
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+			Logger.Log(ioe);
+		}
+
+		template.setOwner(UserSettings.Username);
+		template.resetDateCreated();
+		for (Item i : template.getItems()) {
+			if (ItemUtils.isTag(i, ItemUtils.TAG_PARENT))
+				i.setLink(null);
+		}
+
+		return template;
+	}
+
+	public static void DisableCache() {
+		_UseCache = false;
+	}
+
+	public static void EnableCache() {
+		_UseCache = true;
+	}
+
+	public static void SuspendCache() {
+		if (_UseCache) {
+			DisableCache();
+			_SuspendedCache = true;
+		} else {
+			_SuspendedCache = false;
+		}
+	}
+
+	public static void ResumeCache() {
+		if (_SuspendedCache) {
+			EnableCache();
+			_SuspendedCache = false;
+		}
+	}
+
+	/**
+	 * If the userName ends in a letter a period is appended before adding the
+	 * numeric extension
+	 * 
+	 * @param framesetName
+	 *            the current userName
+	 * @return the exension for the current user
+	 */
+	public static String getFramesetExtension(String framesetName) {
+		if (framesetName.charAt(framesetName.length() - 1) >= '0'
+				&& framesetName.charAt(framesetName.length() - 1) <= '9') {
+			return ".1";
+		}
+		return "1";
+	}
+
+	/**
+	 * Creates a new frameset using the given name. This includes creating a new
+	 * subdirectory in the <code>FRAME_PATH</code> directory, Copying over the
+	 * default.0 frame from the default frameset, copying the .0 Frame to make a
+	 * .1 Frame, and creating the frameset's INF file.
+	 * 
+	 * @param frameset
+	 *            The name of the Frameset to create
+	 * @return The first Frame of the new Frameset (Frame.1)
+	 */
+	public static Frame CreateFrameset(String frameset, String path) {
+		String conversion = frameset + " --> ";
+
+		// ensure the framename is valid
+		frameset = NameValidation(frameset);
+
+		if (frameset == null)
+			return null;
+
+		conversion += frameset;
+		Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Frameset Name: "
+				+ conversion);
+		conversion = frameset;
+
+		/**
+		 * TODO: Update this to exclude any\all invalid filename characters
+		 */
+		// ignore annotation character
+		if (frameset.startsWith("@"))
+			frameset = frameset.substring(1);
+
+		conversion += " --> " + frameset;
+		Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Name: " + conversion);
+
+		// create the new Frameset directory
+		File dir = new File(path + frameset.toLowerCase() + File.separator);
+
+		dir.mkdirs();
+
+		// create the new INF file
+		try {
+			WriteINF(path, frameset, frameset + getFramesetExtension(frameset));
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+			Logger.Log(ioe);
+		}
+
+		SuspendCache();
+		// copy the default .0 and .1 files
+		Frame base;
+		try {
+			base = LoadFrame(UserSettings.DefaultFrame);
+		} catch (Exception e) {
+			base = new Frame();
+		}
+		ResumeCache();
+
+		base.resetDateCreated();
+		base.setFrameset(frameset);
+		base.setFrameNumber(0);
+		base.path = path;
+		SaveFrame(base, false);
+
+		base.resetDateCreated();
+		base.setFrameNumber(1);
+		base.setTitle(frameset);
+		SaveFrame(base, true);
+
+		Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Created new frameset: "
+				+ frameset);
+
+		return base;
+	}
+
+	/**
+	 * Tests if the given String is a 'proper' framename, that is, the String
+	 * must begin with a character, end with a number with 0 or more letters and
+	 * numbers in between. If there is a dot in the framename all the chars
+	 * after it must be digits.
+	 * 
+	 * @param frameName
+	 *            The String to test for validity as a frame name
+	 * @return True if the given framename is proper, false otherwise.
+	 */
+	public static boolean isValidFrameName(String frameName) {
+
+		if (frameName == null || frameName.length() < 2)
+			return false;
+
+		// String must begin with a letter and end with a digit
+		if (!Character.isLetter(frameName.charAt(0))
+				|| !Character.isDigit(frameName.charAt(frameName.length() - 1)))
+			return false;
+
+		int dotIndex = frameName.indexOf(".");
+		// All the characters inbetween first and last must be letters
+		// or digits
+		for (int i = 1; i < frameName.length(); i++) {
+			if (dotIndex > 0 && i > dotIndex) {
+				if (!Character.isDigit(frameName.charAt(i)))
+					return false;
+			} else if (i != dotIndex) {
+				if (!Character.isLetterOrDigit(frameName.charAt(i)))
+					return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Saves the given Frame to disk in the corresponding frameset directory.
+	 * This is the same as calling SaveFrame(toSave, true)
+	 * 
+	 * @param toSave
+	 *            The Frame to save to disk in KMS format.
+	 */
+	public static String SaveFrame(Frame toSave) {
+		return SaveFrame(toSave, true);
+	}
+
+	/**
+	 * Saves the given Frame to disk in the corresponding frameset directory, if
+	 * inc is true then the saved frames counter is incremented, otherwise it is
+	 * untouched.
+	 * 
+	 * @param toSave
+	 *            The Frame to save to disk in KMS format.
+	 * @param inc
+	 *            True if the saved frames counter should be incremented, false
+	 *            otherwise.
+	 */
+	public static String SaveFrame(Frame toSave, boolean inc) {
+
+		if (toSave == null)
+			return "";
+
+		// Get the full path only to determine which format to use for saving
+		// the frame
+		// At this stage use Exp format for saving Exp frames only.
+		// Later this will be changed so that KMS frames will be updated to the
+		// Exp format.
+		String fullPath = getFrameFullPathName(toSave.path, toSave
+				.getFrameName());
+
+		FrameWriter writer = null;
+		try {
+			// if its a new frame or an existing Exp frame...
+			if (fullPath == null || fullPath.endsWith(ExpReader.EXTENTION)) {
+				writer = new ExpWriter();
+			} else {
+				writer = new KMSWriter();
+			}
+
+			if (toSave.getFrameNumber() > 0
+					&& ItemUtils.ContainsTag(toSave.getItems(),
+							ItemUtils.TAG_BACKUP)) {
+				SuspendCache();
+				Frame original = LoadFrame(toSave.getFrameName());
+				if (original == null)
+					original = toSave;
+				int orignum = original.getFrameNumber();
+				int nextnum = ReadINF(toSave.path, toSave.getFramesetName()) + 1;
+
+				original.setFrameNumber(-nextnum);
+				SaveFrame(original, inc);
+
+				Item i = ItemUtils.FindTag(toSave.getItems(),
+						ItemUtils.TAG_BACKUP);
+				i.setLink(original.getFrameName());
+				toSave.setFrameNumber(orignum);
+				ResumeCache();
+			} else if (toSave.getFrameNumber() < 0) {
+				toSave.setFrameNumber(-toSave.getFrameNumber());
+			}
+
+			toSave.setLastModifyDate(Logger.EasyDateFormat("ddMMMyyyy:HHmm"));
+			toSave.setLastModifyUser(UserSettings.Username);
+			writer.writeFrame(toSave);
+			toSave.setSaved();
+			if (inc)
+				SessionStats.SavedFrame(toSave.getFrameName());
+
+			// avoid out-of-sync frames (when in TwinFrames mode)
+			if (_Cache.containsKey(toSave.getFrameName().toLowerCase()))
+				_Cache.put(toSave.getFrameName().toLowerCase(), toSave);
+
+			Logger.Log(Logger.SYSTEM, Logger.SAVE, "Saving "
+					+ toSave.getFrameName() + " to disk.");
+
+			// check that the INF file is not out of date
+			int last = ReadINF(toSave.path, toSave.getFramesetName());
+			if (last <= toSave.getFrameNumber())
+				WriteINF(toSave.path, toSave.getFramesetName(), toSave
+						.getFrameName());
+
+			// check if this was the profile frame (and thus needs
+			// re-parsing)
+			if (toSave.getFramesetName().toLowerCase().equals(
+					UserSettings.Username.toLowerCase())) {
+				FrameUtils.ParseProfile(toSave);
+			}
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+			Logger.Log(ioe);
+		}
+
+		return writer.getFileContents();
+	}
+
+	public static Frame LoadProfile(String userName) {
+		return LoadFrame(userName + FrameIO.getFramesetExtension(userName));
+	}
+
+	public static Frame CreateNewProfile(String username) {
+		Frame profile = CreateFrameset(username, PROFILE_PATH);
+		FrameUtils.CreateDefaultProfile(profile);
+		return profile;
+	}
+
+	/**
+	 * Reads the INF file that corresponds to the given Frame name
+	 * 
+	 * @param framename
+	 *            The Frame to lookup the INF file for
+	 * @throws IOException
+	 *             Any exceptions encountered by the BufferedReader used to read
+	 *             the INF.
+	 */
+	private static int ReadINF(String path, String frameset) throws IOException {
+		assert (!frameset.endsWith("."));
+
+		// read INF
+		BufferedReader reader;
+		try {
+			reader = new BufferedReader(new FileReader(path
+					+ frameset.toLowerCase() + File.separator + INF_FILENAME));
+		} catch (Exception e) {
+			reader = new BufferedReader(new FileReader(path
+					+ frameset.toLowerCase() + File.separator
+					+ frameset.toLowerCase() + ".inf"));
+		}
+		String inf = reader.readLine();
+		reader.close();
+
+		return Conversion.getFrameNumber(inf);
+	}
+
+	/**
+	 * Writes the given String out to the INF file corresponding to the current
+	 * frameset.
+	 * 
+	 * @param toWrite
+	 *            The String to write to the file.
+	 * @throws IOException
+	 *             Any exception encountered by the BufferedWriter.
+	 */
+	private static void WriteINF(String path, String frameset, String frameName)
+			throws IOException {
+		assert (!frameset.endsWith("."));
+
+		path += frameset.toLowerCase() + File.separator + INF_FILENAME;
+
+		BufferedWriter writer = new BufferedWriter(new FileWriter(path));
+		writer.write(frameName);
+		writer.close();
+	}
+
+	public static boolean FrameIsCached(String name) {
+		return _Cache.containsKey(name);
+	}
+
+	private static String NameValidation(String toValidate) {
+		String result = "";
+
+		boolean capital = true;
+		for (int i = 0; i < toValidate.length(); i++) {
+			char cur = toValidate.charAt(i);
+
+			// check for illegal characters
+			if (ILLEGAL_CHARS.contains("" + cur)) {
+				FrameGraphics
+						.DisplayMessage("Frameset name contains illegal character '"
+								+ cur + "' at position " + (i + 1));
+				return null;
+			}
+
+			// capitalize all characters after spaces
+			if (cur == ' ') {
+				capital = true;
+			} else {
+				if (capital) {
+					capital = false;
+					result += ((String) "" + cur).toUpperCase();
+				} else
+					result += cur;
+
+				if (result.length() >= MAX_NAME_LENGTH) {
+					FrameGraphics
+							.DisplayMessage("Frameset name is too long (Max "
+									+ MAX_NAME_LENGTH + " characters)");
+					return null;
+				}
+			}
+		}
+
+		return result;
+	}
+
+	public static Frame CreateNewFrame(Item linker) {
+		String title = null;
+		if (linker instanceof Text)
+			title = ((Text) linker).getFirstLine();
+
+		String frameset = DisplayIO.getCurrentFrame().getFramesetName();
+
+		String templateLink = linker.getLinkTemplate();
+
+		Frame newFrame = FrameIO.CreateFrame(frameset, title, templateLink);
+
+		// do auto shrinking of the title
+		Text titleItem = newFrame.getTitle();
+		
+		while(titleItem.getBoundsWidth() + titleItem.getX() > newFrame.getFrameNameItem().getX() ) {
+			titleItem.setSize(titleItem.getSize() - 1);
+		}
+			
+
+		return newFrame;
+	}
+
+	/**
+	 * Creates a new Frameset on disk, including a .0, .1, and .inf files. The
+	 * Default.0 frame is copied to make the initial .0 and .1 Frames
+	 * 
+	 * @param name
+	 *            The Frameset name to use
+	 * @return The name of the first Frame in the newly created Frameset (the .1
+	 *         frame)
+	 */
+	public static Frame CreateNewFrameset(String name) {
+		String path = DisplayIO.getCurrentFrame().path;
+
+		// if current frameset is profile directory change it to framesets
+		if (path.equals(FrameIO.PROFILE_PATH)) {
+			path = FrameIO.FRAME_PATH;
+		}
+
+		return FrameIO.CreateFrameset(name, path);
+	}
+
+	/**
+	 * 
+	 * @param frameset
+	 * @return
+	 */
+	public static int getLastNumber(String frameset) { // Rob thinks it might
+		// have been
+		// GetHighestNumExFrame
+		// TODO minimise the number of frames being read in!!
+		int num = -1;
+
+		Frame zero = LoadFrame(frameset + "0");
+
+		// the frameset does not exist (or has no 0 frame)
+		if (zero == null)
+			return -1;
+
+		try {
+			num = ReadINF(zero.path, frameset);
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			// e.printStackTrace();
+		}
+
+		/*
+		 * Michael doesnt think the code below is really needed... it will just
+		 * slow things down when we are reading frames over a network***** for (;
+		 * num >= 0; num--) { System.out.println("This code is loading frames to
+		 * find the highest existing frame..."); if (LoadFrame(frameset + num) !=
+		 * null) break; }
+		 */
+
+		return num;
+	}
+
+}
Index: /trunk/src/org/expeditee/gui/FrameKeyboardActions.java
===================================================================
--- /trunk/src/org/expeditee/gui/FrameKeyboardActions.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/FrameKeyboardActions.java	(revision 6)
@@ -0,0 +1,1363 @@
+package org.expeditee.gui;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.expeditee.actions.Actions;
+import org.expeditee.actions.Simple;
+import org.expeditee.io.Logger;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemPermission;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Line;
+import org.expeditee.items.Text;
+import org.expeditee.stats.SessionStats;
+
+public class FrameKeyboardActions implements KeyListener {
+
+	// these numbers correspond to the Function Key numbers (0 = Escape key)
+	public static final int DROP_DOWN = 0;
+
+	public static final int SIZE_UP = 1;
+
+	public static final int SIZE_DOWN = 2;
+
+	public static final int COLOR_CHANGE = 3;
+
+	public static final int ANNOTATION_CHANGE = 4;
+
+	public static final int DATE_ITEM = 5;
+
+	public static final int NEW_FRAMESET = 6;
+
+	public static final int FONT_STYLE_CHANGE = 7;
+
+	public static final int FONT_FAMILY_CHANGE = 8;
+
+	public static final int AUDIENCE_MODE = 9;
+
+	public static final int XRAY_MODE = 10;
+
+	// public static final int RUN_MENU = 11;
+
+	public static final int FORCE_REPAINT = 12;
+
+	public static final int MINIMUM_SIZE = 10;
+
+	public synchronized void keyTyped(KeyEvent e) {
+		if (Simple.isProgramRunning()) {
+			Simple.KeyStroke(e.getKeyChar());
+			return;
+		}
+
+		// ignore escape character and control characters
+		if (e.getKeyChar() == KeyEvent.VK_ESCAPE || e.isControlDown()) {
+			return;
+		}
+		// ingnore auto TDFC key sequence
+		if (e.getKeyChar() == KeyEvent.VK_ENTER && e.isControlDown())
+			return;
+
+		e.consume();
+		char ch = e.getKeyChar();
+
+		if (e.isAltDown()) {
+			if (FrameUtils.getCurrentItem() == null
+					&& !Frame.itemAttachedToCursor()) {
+				Text t = DisplayIO.getCurrentFrame().createNewText(ch + ": ");
+				FrameMouseActions.pickup(t);
+				FrameGraphics.Repaint();
+			} else {
+				// remove the link for alt+l
+				if (ch == 'l') {
+					FrameUtils.getCurrentItem().Item.setLink(null);
+					FrameGraphics.Repaint();
+				}
+			}
+		} else {
+			processChar(ch);
+		}
+		// FrameGraphics.Repaint();
+	}
+
+	private static void processChar(char ch) {
+
+		ItemPermission ip = FrameUtils.getCurrentItem();
+
+		// permission check
+		if (ip != null && ip.Permission < ItemPermission.PERMISSION_FULL) {
+			FrameGraphics
+					.DisplayMessage("Insufficient permission to edit this item");
+			return;
+		}
+
+		Item on = null;
+
+		if (ip != null)
+			on = ip.Item;
+
+		// ignore delete and backspace if in free space
+		if (on == null
+				&& (ch == KeyEvent.VK_BACK_SPACE || ch == KeyEvent.VK_TAB || ch == KeyEvent.VK_DELETE))
+			return;
+
+		SessionStats.TypedChar(ch);
+
+		// check for dot's being replaced with text
+		if (on != null && on instanceof Dot) {
+			replaceDot((Dot) on, ch);
+			return;
+		}
+
+		// only text can interact with keyboard events
+		if (on != null && !(on instanceof Text))
+			on = null;
+
+		Text text = (Text) on;
+
+		// DisplayIO.UpdateTitle();
+
+		// if this text is empty but has not been removed (such as from
+		// ESC-pushdown)
+		if (text != null && text.isEmpty()
+				&& (ch == KeyEvent.VK_BACK_SPACE || ch == KeyEvent.VK_DELETE)) {
+			if (text.getLines().size() > 0)
+				replaceText(text);
+			else {
+				DisplayIO.getCurrentFrame().removeItem(text);
+				DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+			}
+			return;
+		}
+
+		// if the user is in free space, create a new text item
+		if (on == null || !on.isHighlighted()) {
+			SessionStats.CreatedItem();
+			// DisplayIO.UpdateTitle();
+			text = createText(ch);
+			FrameUtils.LastEdited = text;
+			DisplayIO.getCurrentFrame().addItem(text);
+			DisplayIO.setTextCursor(text.getSize());
+			return;
+		}
+
+		DisplayIO.setTextCursor(text.getSize());
+		Point newMouse = text.insertChar(ch, DisplayIO.getMouseX(), DisplayIO
+				.getMouseY());
+		DisplayIO.setCursorPosition(newMouse.x, newMouse.y, false);
+
+		// a change has occured to the Frame
+		text.getParent().setChanged(true);
+
+		// check that the Text item still exists (hasn't been deleted\backspaced
+		// away)
+		if (text.isEmpty()) {
+			if (text.getAction() != null)
+				text.setActionMark(true);
+			else if (text.getLink() != null)
+				text.setLinkMark(true);
+			else if (text.getLines().size() > 0)
+				replaceText(text);
+			else {
+				DisplayIO.getCurrentFrame().removeItem(text);
+				DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+			}
+		}
+
+		FrameUtils.LastEdited = text;
+	}
+
+	private static void replaceDot(Dot dot, char ch) {
+		SessionStats.CreatedItem();
+		Text text = createText(ch);
+		FrameUtils.LastEdited = text;
+
+		List<Line> lines = new LinkedList<Line>();
+		lines.addAll(dot.getLines());
+		for (Line line : lines)
+			line.replaceEnd(dot, text);
+
+		dot.removeAllLines();
+		DisplayIO.getCurrentFrame().removeItem(dot);
+		DisplayIO.getCurrentFrame().addItem(text);
+		return;
+	}
+
+	// replaces the given text item with a dot
+	private static void replaceText(Text text) {
+		Dot dot = new Dot(text.getX(), text.getY(), text.getID());
+		Item.DuplicateItem(text, dot);
+
+		List<Line> lines = new LinkedList<Line>();
+		lines.addAll(text.getLines());
+		for (Line line : lines)
+			line.replaceEnd(text, dot);
+		text.removeAllLines();
+
+		DisplayIO.getCurrentFrame().removeItem(text);
+		DisplayIO.getCurrentFrame().addItem(dot);
+		DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+	}
+
+	/**
+	 * Creates a new Text Item whose text contains the given character. This
+	 * method also moves the mouse cursor to be pointing at the newly created
+	 * Text Item ready to insert the next character.
+	 * 
+	 * @param start
+	 *            The character to use as the initial text of this Item.
+	 * @return The newly created Text Item
+	 */
+	private static Text createText(char start) {
+		Text t = DisplayIO.getCurrentFrame().createBlankText("" + start);
+
+		Point newMouse = t.insertChar(start, DisplayIO.getMouseX(), DisplayIO
+				.getMouseY());
+		DisplayIO.setCursorPosition(newMouse.x, newMouse.y, false);
+
+		return t;
+	}
+
+	/**
+	 * Creates a new Text Item with no text. The newly created Item is a copy of
+	 * any ItemTemplate if one is present, and inherits all the attributes of
+	 * the Template
+	 * 
+	 * @return The newly created Text Item
+	 */
+	private static Text createText() {
+		return DisplayIO.getCurrentFrame().createNewText();
+	}
+
+	private void move(int direction) {
+		ItemPermission ip = FrameUtils.getCurrentItem();
+
+		if (ip == null) {
+			Frame next = (direction == Text.RIGHT) ? FrameIO.LoadNext()
+					: FrameIO.LoadPrevious();
+			FrameUtils.DisplayFrame(next, true);
+			return;
+		}
+
+		Item on = ip.Item;
+		if (on instanceof Text) {
+			// When the user hits the left and right button with mouse
+			// positions over the the frame name navigation occurs
+			if (on.isFrameName()) {
+				Frame next = (direction == Text.RIGHT) ? FrameIO.LoadNext()
+						: FrameIO.LoadPrevious();
+				FrameUtils.DisplayFrame(next, true);
+				return;
+			} else {
+				FrameUtils.LastEdited = on;
+				Point newMouse = ((Text) on).moveCursor(direction, DisplayIO
+						.getMouseX(), DisplayIO.getMouseY());
+				DisplayIO.setTextCursor(((Text) on).getSize());
+				DisplayIO.setCursorPosition(newMouse, false);
+			}
+		}
+	}
+
+	/**
+	 * Receives and processes any Function, Control, and Escape key presses
+	 * 
+	 * @param e
+	 *            The KeyEvent received from the keyboard
+	 */
+	public void keyPressed(KeyEvent e) {
+		SessionStats.AddFrameEvent("k" + KeyEvent.getKeyText(e.getKeyCode()));
+
+		FrameUtils.ResponseTimer.restart();
+		e.consume();
+
+		if (Actions.isAgentRunning()) {
+			if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
+				Actions.stopAgent();
+			else
+				Actions.interruptAgent();
+			return;
+		} else if (Simple.isProgramRunning()) {
+			return;
+		}
+
+		if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+			functionKey(DROP_DOWN);
+			SessionStats.Escape();
+			return;
+		} else if (e.getKeyCode() >= KeyEvent.VK_F1
+				&& e.getKeyCode() <= KeyEvent.VK_F12) {
+			functionKey(e.getKeyCode() - KeyEvent.VK_F1 + 1);
+			return;
+		} else if (e.isControlDown() && e.getKeyCode() != KeyEvent.VK_CONTROL)
+			controlChar(e, KeyEvent.getKeyText(e.getKeyCode()));
+
+		switch (e.getKeyCode()) {
+		case KeyEvent.VK_LEFT:
+			move(Text.LEFT);
+			break;
+		case KeyEvent.VK_RIGHT:
+			move(Text.RIGHT);
+			break;
+		case KeyEvent.VK_UP:
+			if (e.isControlDown() || e.isShiftDown()) {
+				DisplayIO.getCurrentFrame();
+			} else {
+				move(Text.UP);
+			}
+			break;
+		case KeyEvent.VK_DOWN:
+			move(Text.DOWN);
+			break;
+		case KeyEvent.VK_END:
+			move(Text.END);
+			break;
+		case KeyEvent.VK_HOME:
+			move(Text.HOME);
+			break;
+		case KeyEvent.VK_ENTER:
+			if (e.isControlDown()) {
+				FrameMouseActions.leftButton(FrameUtils.getCurrentItem());
+			}
+			break;
+		}
+	}
+
+	/**
+	 * Currently ignored.
+	 */
+	public void keyReleased(KeyEvent e) {
+
+	}
+
+	/**
+	 * Processes all control character keystrokes. Currently Ctrl+C and Ctrl+V
+	 * are copy and paste, all other keystrokes are ignored.
+	 * 
+	 * @param ch
+	 *            The character being pressed along with the control key
+	 */
+	private static void controlChar(KeyEvent e, String ch) {
+		// if this is a paste command
+		if (ch.charAt(0) == KeyEvent.VK_V) {
+			try {
+				// read in the data from the clipboard
+				String clip = ((String) Toolkit.getDefaultToolkit()
+						.getSystemClipboard().getContents(null)
+						.getTransferData(DataFlavor.stringFlavor));
+
+				ItemPermission clicked = FrameUtils.getCurrentItem();
+
+				if (clicked != null) {
+					// check permissions
+					if (clicked.Permission < ItemPermission.PERMISSION_FULL
+							&& clicked.Item.getParent() != null
+							&& clicked.Item.getParent().getName() != clicked.Item) {
+						FrameGraphics.DisplayMessage("Insufficient Permission");
+						return;
+					}
+
+					Text text = createText();
+					text.setText(clip);
+					List<Item> clipboard = new ArrayList<Item>();
+					clipboard.add(text);
+					List<Item> left = FrameMouseActions.merge(clipboard,
+							clicked.Item);
+					FrameMouseActions.anchor(left);
+				} else {
+
+					StringTokenizer st = new StringTokenizer(clip, "\n", true);
+
+					String temp = "";
+					int y = DisplayIO.getMouseY();
+
+					// separate the clipboard content into items based on
+					// blank
+					// lines
+					while (st.hasMoreTokens()) {
+						String s = st.nextToken();
+						// if this is a blank line, then it is an item separator
+						if (s.trim().length() == 0) {
+							if (temp.trim().length() > 0) {
+								Text text = createText();
+								text.setText(temp);
+								text.setY(y);
+								DisplayIO.getCurrentFrame().addItem(text);
+								y = text.getY() + text.getBoundsHeight();
+							}
+							temp = "";
+						} else {
+							if (st.hasMoreTokens()) {
+								temp += s + "\n";
+								st.nextToken(); // Skip '\n' separator
+							} else {
+								temp += s;
+							}
+						}
+					}
+					// the last item will not be finished by the above loop
+					if (temp.trim().length() > 1) {
+						Text text = createText();
+						text.setText(temp);
+						text.setY(y);
+						DisplayIO.getCurrentFrame().addItem(text);
+						temp = "";
+					}
+				}
+
+				FrameGraphics.Repaint();
+			} catch (Exception ex) {
+			}
+			return;
+
+			// if this is a copy command
+		} else if (ch.charAt(0) == KeyEvent.VK_C) {
+			ItemPermission ip = FrameUtils.getCurrentItem();
+
+			if (ip != null && ip.Permission < ItemPermission.PERMISSION_COPY) {
+				FrameGraphics
+						.DisplayMessage("Insufficient permission to copy that item");
+				return;
+			}
+
+			Item on = null;
+			if (ip != null)
+				on = ip.Item;
+
+			if (on == null || !(on instanceof Text))
+				return;
+
+			String text = "";
+
+			List<String> lines = ((Text) on).getText();
+			for (String s : lines)
+				text += s + "\n";
+			// remove the last '\n'
+			text = text.substring(0, text.length() - 1);
+
+			// add the text of the item to the clipboard
+			StringSelection selection = new StringSelection(text);
+			Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
+					selection, null);
+
+			FrameGraphics.DisplayMessage("Item copied to clipboard");
+			return;
+		} else if (ch.charAt(0) == KeyEvent.VK_D) {
+			// perform a delete operation
+			processChar((char) KeyEvent.VK_DELETE);
+			FrameGraphics.Repaint();
+		} else if (ch.charAt(0) == KeyEvent.VK_J) {
+
+			Text text = getCurrentTextItem();
+			if (text == null)
+				return;
+
+			if (text.getWidth() < 0)
+				text.setWidth(text.getBoundsWidth() - Item.MARGIN_RIGHT
+						- UserSettings.Gravity);
+
+			text.setJustification(Item.JUSTIFICATION_LEFT);
+
+			FrameGraphics.Repaint();
+			return;
+		} else if (ch.charAt(0) == KeyEvent.VK_S) {
+			// split the current text item
+			Text text = getCurrentTextItem();
+			if (text == null)
+				return;
+			List<String> textLines = text.getText();
+			if (textLines.size() <= 1)
+				return;
+			// remove all except the first line of text from the item being
+			// split
+			text.setText(textLines.get(0));
+			int y = text.getY();
+			for (int i = 1; i < textLines.size(); i++) {
+				Text newText = text.copy();
+				newText.setText(textLines.get(i));
+				y += newText.getBoundsHeight();
+				newText.setY(y);
+
+				Frame current = DisplayIO.getCurrentFrame();
+				// update the items ID to prevent conflicts with the current
+				// frame
+				newText.setID(current.getNextItemID());
+
+				current.addItem(newText);
+			}
+			FrameGraphics.Repaint();
+
+			return;
+		}
+
+		Logger.Log(Logger.USER, Logger.CONTROL_CHAR, "User pressing: Ctrl+"
+				+ ch);
+	}
+
+	/**
+	 * Gets the currently selected item if the user is allowed to modify it.
+	 * 
+	 * @return null if the currently selected item is not a Text item that the
+	 *         user has permission to modify
+	 */
+	private static Text getCurrentTextItem() {
+		ItemPermission ip = FrameUtils.getCurrentItem();
+
+		if (ip != null && ip.Permission < ItemPermission.PERMISSION_FULL) {
+			FrameGraphics
+					.DisplayMessage("Insufficient permission to copy that item");
+			return null;
+		}
+
+		Item on = null;
+		if (ip != null)
+			on = ip.Item;
+
+		if (on == null || !(on instanceof Text))
+			return null;
+
+		return (Text) on;
+	}
+
+	/**
+	 * Called when a Function key has been pressed, and performs the specific
+	 * action based on the key.
+	 */
+	public static void functionKey(int key) {
+		// get whatever the user is pointing at
+		ItemPermission on = FrameUtils.getCurrentItem();
+
+		// check for enclosed mode
+		if (on == null || on.Item == null) {
+			List<ItemPermission> enclosed = FrameUtils.getCurrentItems();
+
+			if (enclosed != null && enclosed.size() > 0) {
+				// ensure only one dot\line is present in the list
+				List<Dot> dots = FrameUtils.getEnclosingDots();
+				List<Item> connected = dots.get(0).getAllConnected();
+
+				// only resize lines if they are not surrounding some non line
+				// items
+				boolean resizeLines = true;
+				for (ItemPermission ip : enclosed) {
+					if (ip.Item != null) {
+						if (!(ip.Item instanceof Line)
+								&& !(ip.Item instanceof Dot)) {
+							resizeLines = false;
+							break;
+						}
+					}
+				}
+
+				for (ItemPermission ip : enclosed) {
+					if (ip.Item != null && !(connected.indexOf(ip.Item) > 0))
+						switch (key) {
+						case SIZE_UP:
+							if (resizeLines
+									|| (!(ip.Item instanceof Line) && !(ip.Item instanceof Dot)))
+								SetSize(ip, 1, false);
+							break;
+						case SIZE_DOWN:
+							if (resizeLines
+									|| (!(ip.Item instanceof Line) && !(ip.Item instanceof Dot)))
+								SetSize(ip, -1, false);
+							break;
+						// case COLOR_CHANGE: SetFillColor(ip); break;
+						case ANNOTATION_CHANGE:
+							ToggleAnnotation(ip);
+							break;
+						case FONT_STYLE_CHANGE:
+							ToggleFontStyle(ip);
+							break;
+						case FONT_FAMILY_CHANGE:
+							ToggleFontFamily(ip);
+							break;
+						case DATE_ITEM:
+							AddDate(ip);
+							break;
+						}
+				}
+
+				if (key == COLOR_CHANGE && connected.size() > 0) {
+					for (Dot d : dots) {
+						SetFillColor(d);
+						break;
+					}
+				}
+				return;
+			}
+		}
+
+		switch (key) {
+		case DROP_DOWN:
+			Drop(on);
+			break;
+		case SIZE_UP:
+			SetSize(on, 1, true);
+			break;
+		case SIZE_DOWN:
+			SetSize(on, -1, true);
+			break;
+		case COLOR_CHANGE:
+			SetColor(on);
+			break;
+		case ANNOTATION_CHANGE:
+			ToggleAnnotation(on);
+			break;
+		case FONT_STYLE_CHANGE:
+			ToggleFontStyle(on);
+			break;
+		case FONT_FAMILY_CHANGE:
+			ToggleFontFamily(on);
+			break;
+		case DATE_ITEM:
+			AddDate(on);
+			break;
+		case NEW_FRAMESET:
+			CreateFrameset(on);
+			break;
+		case XRAY_MODE:
+			ToggleXRayMode(on);
+			break;
+		case AUDIENCE_MODE:
+			ToggleAudience(on);
+			break;
+		// case RUN_MENU:
+		// RunFirstMenuItem(on);
+		// break;
+		case FORCE_REPAINT:
+			Repaint(on);
+			break;
+		}
+	}
+
+	public static final String DEFAULT_NEW_ITEM_TEXT = "";
+
+	/**
+	 * Performs the dropping action: If the cursor is in free space then: the
+	 * cursor is repositioned below the last non-annotation text item. If the
+	 * cursor is on an item, and has items attached then: the cusor is
+	 * positioned below the pointed to item, and the items below are 'pushed
+	 * down' to make room.
+	 * 
+	 * @param toDropFrom
+	 *            The Item being pointed at by the mouse, may be null to
+	 *            indicate the cursor is in free space.
+	 */
+	private static void Drop(ItemPermission ip) {
+		String newItemText = DEFAULT_NEW_ITEM_TEXT;
+
+		// if a line is being rubber-banded, this is a no-op
+		if (Frame.rubberbandingLine())
+			return; // No-op
+
+		// drop requires no permissions
+		Item toDropFrom = null;
+		if (ip != null)
+			toDropFrom = ip.Item;
+
+		// if the cursor is in free space then the drop will happen from the
+		// last non annotation text item on the frame
+		if (toDropFrom == null) {
+			toDropFrom = DisplayIO.getCurrentFrame()
+					.getLastNonAnnotationTextItem();
+		}
+
+		// if no item was found, return
+		if (toDropFrom == null) {
+			FrameGraphics.ErrorMessage("No item could be found to drop from");
+			return;
+		}
+
+		if (!(toDropFrom instanceof Text)) {
+			FrameGraphics.DisplayMessage("Only text items can be dropped from");
+			return;
+		}
+
+		// Get the list of items that must be dropped
+		List<Item> column = DisplayIO.getCurrentFrame().getColumn(toDropFrom);
+
+		if (column == null) {
+			FrameGraphics.ErrorMessage("No column found to align items to");
+			return;
+		}
+
+		Text title = DisplayIO.getCurrentFrame().getTitle();
+
+		// We wont do auto bulleting when dropping from titles
+		if (toDropFrom != title) {
+			newItemText = getAutoBullet(((Text) toDropFrom).getFirstLine());
+		}
+
+		Text dummyItem = null;
+
+		if (Frame.textItemAttachedToCursor()) {
+			dummyItem = (Text) Frame.getItemAttachedToCursor();
+			String autoBullet = getAutoBullet(dummyItem.getTextNoList());
+
+			if (autoBullet.length() > 0) {
+				dummyItem.stripFirstWord();
+			}
+			dummyItem.setText(newItemText + dummyItem.getTextNoList());
+		}
+
+		dummyItem = createText();
+		dummyItem.setText(newItemText);
+
+		// If the only item on the frame is the title and the frame name just
+		// drop a specified distance below the title
+		if (column.size() == 0) {
+			Text itemTemplate = DisplayIO.getCurrentFrame().getItemTemplate();
+			int xPos = title.getX();
+			int yPos = title.getY() + title.getBoundsHeight()
+					+ itemTemplate.getBoundsHeight();
+			dummyItem.setPosition(xPos, yPos);
+			DisplayIO.setCursorPosition(xPos, yPos);
+		} else {
+
+			int yPos = column.get(0).getY() + 1;
+
+			// Either position the new item below the title or just above
+			// the first item below the title
+			if (toDropFrom == title)
+				yPos = Math
+						.min(column.get(0).getY() - 1, title.getY()
+								+ title.getBoundsHeight()
+								+ dummyItem.getBoundsHeight());
+
+			dummyItem.setPosition(column.get(0).getX(), yPos);
+			column.add(dummyItem);
+
+			FrameUtils.Align(column, false, 0);
+			// Check if it will be outside the frame area
+			if (dummyItem.getY() < 0
+					|| dummyItem.getY() > FrameGraphics.getMaxFrameSize()
+							.getHeight()) {
+				// Check for the @more tag!
+				Item i = ItemUtils.FindTag(DisplayIO.getCurrentFrame()
+						.getItems(), "@More");
+				if (i != null) {
+					Frame firstFrame = DisplayIO.getCurrentFrame();
+					boolean mouseMoved = FrameMouseActions.tdfc(i);
+					Frame moreFrame = DisplayIO.getCurrentFrame();
+					moreFrame.setTitle(firstFrame.getTitle().getTextNoList());
+					//need to move the mouse to the top of the frame if there wasnt an @start on it
+					if (!mouseMoved) {
+						Drop(new ItemPermission(moreFrame.getTitle(), ItemPermission.PERMISSION_FULL));
+					}
+					
+					// Add the bullet text to the item
+					dummyItem.setPosition(DisplayIO.getMouseX(),
+							DisplayIO.getMouseY());
+				} else {
+					FrameGraphics
+							.WarningMessage("Can not create items outside the frame area");
+					// ensures correct repainting when items don't move
+					DisplayIO.setCursorPosition(DisplayIO.getMouseX(),
+							DisplayIO.getMouseY());
+					return;
+				}
+			}
+			if (!Frame.textItemAttachedToCursor() && !dummyItem.isEmpty()) {
+				DisplayIO.getCurrentFrame().addItem(dummyItem);
+			}
+
+			// Move the item to the cursor position
+			if (Frame.itemAttachedToCursor()) {
+				DisplayIO.setCursorPosition(dummyItem.getX(), dummyItem.getY());
+				Frame.getItemAttachedToCursor().setPosition(dummyItem.getX(),
+						dummyItem.getY());
+			} else {
+				DisplayIO
+						.setCursorPosition(
+								dummyItem.getEndParagraphPosition().x,
+								dummyItem.getY());
+			}
+		}
+
+		DisplayIO.resetCursorOffset();
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Gets the next letter sequence for a given string to be used in auto
+	 * lettering.
+	 * 
+	 * @param s
+	 *            a sequence of letters
+	 * @return the next sequence of letters
+	 */
+	static private String nextLetterSequence(String s) {
+		if (s.length() > 1)
+			return s;
+
+		if (s.equals("z"))
+			return "a";
+
+		return (char) ((int) s.charAt(0) + 1) + "";
+	}
+
+	public static String getBullet(String s) {
+		return getBullet(s, false);
+	}
+
+	public static String getAutoBullet(String s) {
+		return getBullet(s, true);
+	}
+
+	private static String getBullet(String s, boolean nextBullet) {
+		String newItemText = DEFAULT_NEW_ITEM_TEXT;
+
+		/*
+		 * Item i = ItemUtils.FindTag(DisplayIO.getCurrentFrame().getItems(),
+		 * "@NoAutoBullets"); if (i != null) return newItemText;
+		 */
+
+		// figure out the type of the text item
+		// This allows us to do auto bulleting
+		if (s != null && s.length() > 1) {
+			// First check for text beginning with * @ # etc
+			// These are simple auto bullets
+			if (!Character.isLetterOrDigit(s.charAt(0))
+					&& !Character.isSpaceChar(s.charAt(0))) {
+				if (s.charAt(0) == '*' || s.charAt(0) == '+'
+						|| s.charAt(0) == '-' || s.charAt(0) == '>'
+						|| s.charAt(0) == '=' || s.charAt(0) == '@'
+						|| s.charAt(0) == '~' || s.charAt(0) == '#') {
+					int nonSpaceIndex = 1;
+					while (nonSpaceIndex < s.length()
+							&& s.charAt(nonSpaceIndex) == ' ') {
+						nonSpaceIndex++;
+					}
+					// we must have a special char followed by >= 1 space
+					if (nonSpaceIndex > 1)
+						newItemText = s.substring(0, nonSpaceIndex);
+				}
+				// Auto numbering and lettering
+			} else {
+				if (Character.isDigit(s.charAt(0))) {
+					newItemText = getAutoNumber(s, nextBullet);
+					// Auto lettering
+				} else if (Character.isLetter(s.charAt(0))) {
+					newItemText = getAutoLetter(s, nextBullet);
+				}
+			}
+		}
+		return newItemText;
+	}
+
+	private static boolean isAutoNumberOrLetterChar(char c) {
+		return c == ':' || c == '-' || c == '.' || c == ')' || c == '>';
+	}
+
+	/**
+	 * Gets the string to be used to start the next auto numbered text item.
+	 * 
+	 * @param s
+	 *            the previous text item
+	 * @return the beginning of the next auto numbered text item
+	 */
+	private static String getAutoNumber(String s, boolean nextBullet) {
+		String newItemText = DEFAULT_NEW_ITEM_TEXT;
+
+		int nonDigitIndex = 1;
+		while (Character.isDigit(s.charAt(nonDigitIndex))) {
+			nonDigitIndex++;
+
+			if (nonDigitIndex + 1 >= s.length())
+				return DEFAULT_NEW_ITEM_TEXT;
+		}
+
+		if (isAutoNumberOrLetterChar(s.charAt(nonDigitIndex))) {
+
+			// we must have a number followed one non letter
+			// then one or more spaces
+			int nonSpaceIndex = nonDigitIndex + 1;
+			while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') {
+				nonSpaceIndex++;
+			}
+
+			if (nonSpaceIndex > nonDigitIndex + 1) {
+				if (nextBullet)
+					newItemText = (Integer.parseInt(s.substring(0,
+							nonDigitIndex)) + 1)
+							+ s.substring(nonDigitIndex, nonSpaceIndex);
+				else
+					newItemText = s.substring(0, nonSpaceIndex);
+			}
+		}
+		return newItemText;
+	}
+
+	/**
+	 * Gets the string to be used to start the next auto lettered text item.
+	 * 
+	 * @param s
+	 *            the previous text items
+	 * @return the initial text for the new text item
+	 */
+	private static String getAutoLetter(String s, boolean nextBullet) {
+		String newItemText = DEFAULT_NEW_ITEM_TEXT;
+
+		int nonLetterIndex = 1;
+
+		if (isAutoNumberOrLetterChar(s.charAt(nonLetterIndex))) {
+
+			// Now search for the next non space character
+			int nonSpaceIndex = nonLetterIndex + 1;
+			while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') {
+				nonSpaceIndex++;
+			}
+
+			// If there was a space then we have reached the end of our auto
+			// text
+			if (nonSpaceIndex > nonLetterIndex + 1) {
+				if (nextBullet)
+					newItemText = nextLetterSequence(s.substring(0,
+							nonLetterIndex))
+							+ s.substring(nonLetterIndex, nonSpaceIndex);
+				else
+					newItemText = s.substring(0, nonSpaceIndex);
+			}
+		}
+		return newItemText;
+	}
+
+	/**
+	 * Adjusts the size of the given Item, by the given amount. Note: The amount
+	 * is relative and can be positive or negative.
+	 * 
+	 * @param toSet
+	 *            The Item whose size is to be adjusted
+	 * @param diff
+	 *            The amount to adjust the Item's size by
+	 * @param moveCursor
+	 *            true if the cursor position should be automatically adjusted
+	 *            with resizing
+	 */
+	private static void SetSize(ItemPermission ip, int diff, boolean moveCursor) {
+		List<Item> toSize;
+		// the mouse is only moved when the Item is on the frame, not free
+		// boolean moveMouse = false;
+		Item toSet = null;
+
+		// if the user is not pointing to any item
+		if (ip == null) {
+			if (Frame.itemAttachedToCursor())
+				toSize = new ArrayList<Item>(Frame.FreeItems);
+			else {
+				FrameGraphics
+						.DisplayMessage("There are no Items selected on the Frame or on the Cursor");
+				return;
+			}
+		} else {
+			// check permissions
+			if (ip.Permission < ItemPermission.PERMISSION_FULL) {
+				FrameGraphics
+						.DisplayMessage("Insufficient permission to change the size of that item");
+				return;
+			}
+			toSet = ip.Item;
+
+			toSize = toSet.getConnected();
+			// moveMouse = true;
+		}
+
+		if (toSet instanceof Line) {
+			Line line = (Line) toSet;
+			float current = Math.abs(line.getThickness());
+			current = Math.max(current + diff, 1);
+			line.setThickness(current);
+			FrameGraphics.Repaint();
+			return;
+		}
+
+		/*
+		 * for(Item i : toSize){ if(i instanceof Dot || i instanceof Line){
+		 * toSize.removeAll(i.getConnected()); toSize.add(i); } }
+		 */
+
+		int old_width = 0;
+		int old_height = 0;
+
+		if (toSet != null) {
+			old_width = toSet.getBoundsWidth();
+			old_height = toSet.getBoundsHeight();
+		}
+
+		// adjust the size of all the items
+		for (Item i : toSize) {
+			// Lines and dots use thickness, not size
+			if (i instanceof Line) {
+				Line line = (Line) i;
+				float current = Math.abs(line.getThickness());
+				current = Math.max(current + diff, 1);
+				line.setThickness(current);
+			} else if (i instanceof Dot) {
+				Dot dot = (Dot) i;
+				float current = Math.abs(dot.getThickness());
+				current = Math.max(current + diff, 1);
+				dot.setThickness(current);
+			} else {
+				int current = Math.abs(i.getSize());
+				current = Math.max(current + diff, 1);
+				if (current > MINIMUM_SIZE)
+					i.setSize(current);
+			}
+		}
+
+		// center the mouse cursor on the item
+		if (moveCursor && toSet != null) {
+			if (!toSet.contains(DisplayIO.getMouseX(), DisplayIO.getMouseY())) {
+				int x = DisplayIO.getMouseX();
+				int y = DisplayIO.getMouseY();
+
+				if (!toSet.contains(x, toSet.getY()))
+					x = x - (old_width - toSet.getBoundsWidth());
+
+				// text grows 'up', pictures grow 'down'
+				int direction = -1;
+				if (toSet instanceof Text)
+					direction = 1;
+
+				if (!toSet.contains(toSet.getX(), y))
+					y = y
+							+ (direction * (old_height - toSet
+									.getBoundsHeight()));
+
+				DisplayIO.setCursorPosition(x, y);
+			}
+
+			// int x = toSet.getX() + toSet.getBoundsWidth() / 2;
+			// int y = toSet.getY() + toSet.getBoundsHeight() / 2;;
+			// DisplayIO.setCursorPosition(x, y);
+
+		}
+
+		if (toSet != null)
+			toSet.getParent().setChanged(true);
+
+		FrameGraphics.Repaint();
+	}
+
+	private static void SetFillColor(Item item) {
+		if (item == null)
+			return;
+
+		Item toSet = item;// .Item;
+		Color color = toSet.getFillColor();
+
+		color = ColorUtils.getNextColor(color, Item.COLOR_WHEEL);
+
+		// TODO what happens if the above statement returns null??
+
+		toSet.setFillColor(color);
+		toSet.getParent().setChanged(true);
+
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Sets the colour of the current Item based on its current colour. The
+	 * colours proceed in the order stored in COLOR_WHEEL.
+	 * 
+	 * @param toSet
+	 *            The Item whose colour is to be changed
+	 */
+	private static void SetColor(ItemPermission ip) {
+		// first determine the next color
+		Color color = null;
+
+		if (ip == null) {
+			if (Frame.itemAttachedToCursor()) {
+				color = Frame.FreeItems.get(0).getColor();
+			} else {
+				return;
+			}
+			// change the background color if the user is pointing on the
+			// frame name
+		} else if (ip.Item == DisplayIO.getCurrentFrame().getName()) {
+			DisplayIO.getCurrentFrame().toggleBackgroundColor();
+			return;
+		} else {
+			// check permissions
+			if (ip.Permission < ItemPermission.PERMISSION_FULL) {
+				FrameGraphics
+						.DisplayMessage("Insufficient permission to change that item's color");
+				return;
+			}
+			color = ip.Item.getColor();
+		}
+
+		color = ColorUtils.getNextColor(color, Item.COLOR_WHEEL);
+
+		// if we didnt find the color on the wheel
+		if (color == null) {
+			color = DisplayIO.getCurrentFrame().getItemTemplate().getColor();
+		}
+
+		if (Frame.itemAttachedToCursor()) {
+			for (Item i : Frame.FreeItems)
+				i.setColor(color);
+		} else {
+			Item toSet = ip.Item;
+			toSet.setColor(color);
+			toSet.getParent().setChanged(true);
+
+		}
+
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Toggles the given Item's annotation status on\off.
+	 * 
+	 * @param toToggle
+	 *            The Item to toggle
+	 */
+	private static void ToggleAnnotation(ItemPermission toToggle) {
+		if (toToggle == null) {
+			FrameGraphics.DisplayMessage("There is no Item selected to toggle");
+			return;
+		}
+
+		// check permissions
+		if (toToggle.Permission < ItemPermission.PERMISSION_FULL) {
+			FrameGraphics
+					.DisplayMessage("Insufficient permission to toggle that item's annotation");
+			return;
+		}
+
+		toToggle.Item.setAnnotation(!toToggle.Item.isAnnotation());
+		toToggle.Item.getParent().setChanged(true);
+		FrameGraphics.Repaint();
+	}
+
+	/**
+	 * Toggles the face style of a text item
+	 * 
+	 * @param toToggle
+	 *            The Item to toggle
+	 */
+	private static void ToggleFontStyle(ItemPermission toToggle) {
+		if (toToggle == null) {
+			FrameGraphics.DisplayMessage("There is no Item selected to toggle");
+			return;
+		}
+
+		// check permissions
+		if (toToggle.Permission < ItemPermission.PERMISSION_FULL) {
+			FrameGraphics
+					.DisplayMessage("Insufficient permission to toggle that item's annotation");
+			return;
+		}
+
+		if (toToggle.Item instanceof Text) {
+			Text text = (Text) toToggle.Item;
+			text.toggleFontStyle();
+
+			text.getParent().setChanged(true);
+			FrameGraphics.Repaint();
+		}
+	}
+
+	/**
+	 * Toggles the face style of a text item
+	 * 
+	 * @param toToggle
+	 *            The Item to toggle
+	 */
+	private static void ToggleFontFamily(ItemPermission toToggle) {
+		if (toToggle == null) {
+			FrameGraphics.DisplayMessage("There is no Item selected to toggle");
+			return;
+		}
+
+		// check permissions
+		if (toToggle.Permission < ItemPermission.PERMISSION_FULL) {
+			FrameGraphics
+					.DisplayMessage("Insufficient permission to toggle that item's annotation");
+			return;
+		}
+
+		if (toToggle.Item instanceof Text) {
+			Text text = (Text) toToggle.Item;
+			text.toggleFontFamily();
+
+			text.getParent().setChanged(true);
+			FrameGraphics.Repaint();
+		}
+	}
+
+	/**
+	 * If the given Item is null, then a new Text item is created with the
+	 * current date If the given Item is not null, then the current date is
+	 * prepended to the Item's text
+	 * 
+	 * @param toAdd
+	 *            The Item to prepend the date to, or null
+	 */
+	private static void AddDate(ItemPermission toAdd) {
+		String date1 = Logger.EasyDateFormat("ddMMMyyyy[HH:mm]");
+		String date2 = Logger.EasyDateFormat("ddMMMyyyy");
+		final String leftSeparator = " :";
+		final String rightSeparator = ": ";
+		String dateToAdd = date1 + rightSeparator;
+		boolean prepend = false;
+		boolean append = false;
+
+		// if the user is pointing at an item, add the date where ever the cursor is pointing
+		if (toAdd != null) {
+			if (toAdd.Item instanceof Text) {
+				// permission check
+				if (toAdd.Permission < ItemPermission.PERMISSION_FULL) {
+					FrameGraphics
+							.DisplayMessage("Insufficicent permission to add the date to that item");
+					return;
+				}
+				
+				Text textItem = (Text) toAdd.Item;
+				
+				String text = textItem.getTextNoList();
+
+				// check if the default date has already been put on this item
+				if (text.startsWith(date1 + rightSeparator)) {
+					textItem.removeText(date1 + rightSeparator);
+					dateToAdd = date2 + rightSeparator;
+					prepend = true;
+				} else if (text.startsWith(date2 + rightSeparator)) {
+					textItem.removeText(date2 + rightSeparator);
+					dateToAdd = leftSeparator + date2;
+					append = true;
+				} else if (text.endsWith(leftSeparator + date2)) {
+					textItem.removeEndText(leftSeparator + date2);
+					append = true;
+					dateToAdd = leftSeparator + date1;
+				} else if (text.endsWith(leftSeparator + date1)) {
+					textItem.removeEndText(leftSeparator + date1);
+					if (textItem.getLength() > 0) {
+						dateToAdd = "";
+						prepend = true;
+					} else {
+						// use the default date format
+						prepend = true;
+					}
+				}
+				
+				if (prepend) {
+					// add the date to the text item
+					textItem.prependText(dateToAdd);
+					if (dateToAdd.length() == textItem.getLength())
+						DisplayIO.setCursorPosition(textItem
+								.getEndParagraphPosition());
+				} else if (append) {
+					textItem.appendText(dateToAdd);
+					if (dateToAdd.length() == textItem.getLength())
+						DisplayIO.setCursorPosition(textItem.getPosition());
+				} else {
+					for(int i = 0; i < date1.length(); i++) {
+						processChar(date1.charAt(i));
+					}
+				}
+
+				textItem.getParent().setChanged(true);
+				FrameGraphics.Repaint();
+			} else {
+				FrameGraphics
+						.DisplayMessage("Only text items can have the date prepended to them");
+			}
+			// otherwise, create a new text item
+		} else {
+			Text newText = createText();
+			newText.setText(dateToAdd);
+			DisplayIO.getCurrentFrame().addItem(newText);
+			DisplayIO.getCurrentFrame().setChanged(true);
+			FrameGraphics.Repaint();
+
+			DisplayIO.setCursorPosition(newText.getEndParagraphPosition());
+		}
+
+	}
+
+	/**
+	 * Creates a new Frameset with the name given by the Item
+	 * 
+	 * @param name
+	 */
+	private static void CreateFrameset(ItemPermission ip) {
+		if (ip == null || !(ip.Item instanceof Text)) {
+			FrameGraphics
+					.DisplayMessage("There is no selected item to use for the Frameset name");
+			return;
+		}
+
+		// check permissions
+		if (ip.Permission < ItemPermission.PERMISSION_FULL) {
+			FrameGraphics
+					.DisplayMessage("Insufficient permission to create a frameset from this item");
+			return;
+		}
+
+		Text text = (Text) ip.Item;
+
+		// create the new frameset
+		Frame linkTo = FrameIO.CreateNewFrameset(text.getFirstLine());
+		DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+
+		// if the frameset was created successfully
+		if (linkTo != null) {
+			text.setLink(linkTo.getFrameName());
+
+			text.getParent().setChanged(true);
+			FrameUtils.DisplayFrame(linkTo, true);
+		} else {
+			FrameGraphics
+					.ErrorMessage("An error occured when trying to create the new frameset");
+		}
+	}
+
+	/**
+	 * Toggles Audience mode on\off
+	 * 
+	 * @param ignored
+	 *            This Item is currently ignored
+	 */
+	private static void ToggleAudience(ItemPermission ignored) {
+		FrameGraphics.ToggleAudienceMode();
+	}
+
+	private static void ToggleXRayMode(ItemPermission ignored) {
+		FrameGraphics.ToggleXRayMode();
+	}
+
+	/**
+	 * Runs the first Item on the first menu
+	 * 
+	 * @param ignored
+	 *            This Item is currently ignored
+	 */
+	/*
+	 * private static void RunFirstMenuItem(ItemPermission ignored) {
+	 * DisplayIO.activateMenuItem(0, 0); }
+	 */
+
+	/**
+	 * Forces a re-parse and repaint of the current Frame.
+	 * 
+	 * @param ignored
+	 *            This Item is currently ignored
+	 */
+	private static void Repaint(ItemPermission ignored) {
+		FrameUtils.Parse(DisplayIO.getCurrentFrame());
+		FrameGraphics.ForceRepaint();
+	}
+}
Index: /trunk/src/org/expeditee/gui/FrameMouseActions.java
===================================================================
--- /trunk/src/org/expeditee/gui/FrameMouseActions.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/FrameMouseActions.java	(revision 6)
@@ -0,0 +1,1881 @@
+package org.expeditee.gui;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.Timer;
+
+import org.expeditee.actions.Actions;
+import org.expeditee.actions.NavigationActions;
+import org.expeditee.items.Constraint;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemPermission;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Line;
+import org.expeditee.items.Picture;
+import org.expeditee.items.Text;
+import org.expeditee.stats.SessionStats;
+
+public class FrameMouseActions implements MouseListener, MouseMotionListener,
+		MouseWheelListener {
+
+	// TODO say where/how used
+	private static final int MOUSE_WHEEL_THRESHOLD = 3;
+
+	private static final int MINIMUM_RANGE_DEPRESS_TIME = 250;
+
+	private static final int RECTANGLE_TO_POINT_THRESHOLD = 20;
+
+	private static Date _lastMouseClickDate = new Date();
+
+	public static final int LITTLE_MOUSE_PAUSE = 500;
+
+	public static final int ZERO_MOUSE_PAUSE = 0;
+
+	public static final int BIG_MOUSE_PAUSE = 750;
+
+	public static final int CONTEXT_FREESPACE = 0;
+
+	public static final int CONTEXT_AT_TEXT = 1;
+
+	public static final int CONTEXT_AT_LINE = 2;
+
+	public static final int CONTEXT_AT_DOT = 3;
+
+	public static final int CONTEXT_AT_ENCLOSURE = 4;
+
+	public static int _alpha = -1;
+
+	/**
+	 * The last known mouse X coordinate
+	 */
+	public static int MouseX;
+
+	/**
+	 * The last known mouse Y coordinate. Relative to the top of the
+	 * application.
+	 */
+	public static int MouseY;
+
+	// Offset coordinates from the mouse position when grabbing items
+	private static int _offX;
+
+	private static int _offY;
+
+	// Keeps track of mouse button events when a delete occurs
+	private static boolean _isDelete = false;
+
+	// Keeps track of mouse button events when the user extracts attributes
+	// occurs
+	private static boolean _isAttribute = false;
+
+	/**
+	 * A flag to indicate that the last mouseUp event was part of a two button
+	 * click sequence hence the next mouse up should be ignored*
+	 */
+	private static boolean _wasDouble = false;
+
+	private static boolean _isNoOp = false;
+
+	// keeps track of the last highlighted Item
+	private static Item _lastHighlightedItem = null;
+
+	// keeps track of the item being 'ranged out' if there is one.
+	private static Text _lastRanged = null;
+
+	// keeps track of the picture being cropped if there is one
+	private static Picture _lastCropped = null;
+
+	// true if lastItem only has highlighting removed when a new item is
+	// highlighted
+	private static boolean _lastHoldsHighlight = false;
+
+	private static Item _tdfcItem = null;
+
+	private static boolean _forceArrowCursor = true;
+
+	// the current context of the cursor
+	private static int _context = 0;
+
+	public static void setForceArrow(boolean val) {
+		_forceArrowCursor = val;
+	}
+
+	public static int getContext() {
+		return _context;
+	}
+
+	private static MouseEvent _lastMouseClick = null;
+
+	private static ItemPermission _lastClickedOn = null;
+
+	private static List<ItemPermission> _lastClickedIn = null;
+
+	private static boolean _pulseOn = false;
+
+	private static int _pulseAmount = 2;
+
+	private static Timer _MouseTimer = new Timer(LITTLE_MOUSE_PAUSE,
+			new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					// check if we are in free space
+					if (_lastClickedOn == null && Frame.FreeItems.size() == 0) {
+						System.out.println("SuperBack!");
+						leftButton(null);
+						_MouseTimer.setDelay(ZERO_MOUSE_PAUSE);
+					} else {
+						_pulseOn = !_pulseOn;
+
+						if (_MouseTimer.getDelay() == LITTLE_MOUSE_PAUSE)
+							_MouseTimer.setDelay(LITTLE_MOUSE_PAUSE + 1);
+						else
+							_MouseTimer.setDelay(BIG_MOUSE_PAUSE);
+						if (FrameUtils.getCurrentItem() == null) {
+							for (Item i : Frame.FreeItems) {
+								if (i instanceof Line) {
+									Line line = (Line) i;
+									if (_pulseAmount < 4
+											&& line.getThickness() < 0)
+										_pulseAmount = 4;
+
+									if (_pulseOn)
+										line.setThickness(line.getThickness()
+												+ _pulseAmount);
+									else
+										line.setThickness(line.getThickness()
+												- _pulseAmount);
+
+									line.toggleArrow();
+								}
+							}
+							FrameGraphics.Repaint();
+						}
+						_MouseTimer.stop();
+					}
+				}
+			});
+
+	public void mouseClicked(MouseEvent e) {
+	}
+
+	/**
+	 * Each Item on the Frame is checked to determine if the mouse x,y
+	 * coordinates are on the Item (or within the Shape surrounding it). If the
+	 * coordinates are on the Item then the Item is checked for a link, if it
+	 * has a link the link is followed, if not, nothing is done.
+	 */
+	public void mousePressed(MouseEvent e) {
+		_lastMouseClickDate = new Date();
+		_lastClickedOn = FrameUtils.getCurrentItem();
+		// load any frame if necessary
+		ItemPermission on = _lastClickedOn;
+
+		_lastClickedIn = FrameUtils.getCurrentItems();
+
+		SessionStats.MouseClicked(e.getButton());
+		if (e.getButton() == MouseEvent.BUTTON1) {
+			SessionStats.AddFrameEvent("Ld");
+		} else if (e.getButton() == MouseEvent.BUTTON2) {
+			SessionStats.AddFrameEvent("Md");
+		} else if (e.getButton() == MouseEvent.BUTTON3) {
+			SessionStats.AddFrameEvent("Rd");
+		}
+
+		_lastMouseClick = e;
+		_MouseTimer.start();
+
+		// pre-cache the frame if it is linked
+		if (on != null && on.Item.getLink() != null && on.Item.isLinkValid()) {
+			FrameIO.Precache(on.Item.getLink());
+		}
+
+		// check for delete command
+		if (isDelete(e)) {
+			_isDelete = true;
+			// _lastRanged = null;
+			_lastCropped = null;
+			_wasDouble = false;
+			// check for attributes command
+		} else if (isGetAttributes(e)) {
+			_isAttribute = true;
+			_wasDouble = false;
+		} else if (isTwoClickNoOp(e)) {
+			_isAttribute = false;
+			_wasDouble = false;
+			_isDelete = false;
+			_isNoOp = true;
+		} else
+			_isDelete = false;
+
+		{
+			// This must happen before the previous code
+			if (_context == CONTEXT_FREESPACE && Frame.itemAttachedToCursor()) {
+				_lastHighlightedItem = Frame.getItemAttachedToCursor();
+			}
+			// show depressed highlighting
+			if (_lastHighlightedItem != null) {
+				_lastHighlightedItem.showDepressedHighlight(true);
+				FrameGraphics.Repaint();
+			}
+		}
+
+		// if the user is ranging text
+		if (on != null && on.Item instanceof Text && !_isDelete) {
+			_lastRanged = (Text) on.Item;
+			// set start-drag point
+			_lastRanged.setSelectionStart(DisplayIO.getMouseX(), DisplayIO
+					.getMouseY());
+		}
+
+		if (on != null && on.Item instanceof Picture
+				&& e.getButton() == MouseEvent.BUTTON3 && !_isDelete) {
+			_lastCropped = (Picture) on.Item;
+			// set start crop point
+			_lastCropped.setStartCrop(DisplayIO.getMouseX(), DisplayIO
+					.getMouseY());
+			_lastCropped.setShowCrop(true);
+		}
+	}
+
+	// This is where all the processing happens
+	public void mouseReleased(MouseEvent e) {
+		updateCursor();
+
+		Text lastRanged = _lastRanged;
+		_lastRanged = null;
+		// Dont do ranging if the user moves really quickly...
+		// They are probably trying to pick something up in this case
+		if (lastRanged != null) {
+			long depressTime = (new Date()).getTime()
+					- _lastMouseClickDate.getTime();
+			// double changeInDistance =
+			// e.getPoint().distance(_currentMouseClick.getPoint());
+			// double speed = changeInDistance * 1000 / changeInTime;
+
+			// System.out.println(depressTime);
+
+			if (depressTime < MINIMUM_RANGE_DEPRESS_TIME
+					|| lastRanged.getSelectedSize() <= 0) {// Text.MINIMUM_RANGED_CHARS)
+				// {
+				lastRanged.clearSelection();
+				lastRanged = null;
+			}
+		}
+
+		FrameUtils.ResponseTimer.restart();
+
+		_MouseTimer.stop();
+		_MouseTimer.setDelay(LITTLE_MOUSE_PAUSE);
+
+		if (_pulseOn) {
+			for (Item i : Frame.FreeItems) {
+				if (i instanceof Line) {
+					Line line = (Line) i;
+					line.setThickness(line.getThickness() - _pulseAmount);
+				}
+			}
+			_pulseOn = false;
+		}
+
+		// if the user was performing actions, stop now
+		if (_pulseAmount < 0) {
+			_pulseAmount = 2;
+			return;
+		}
+
+		// reset pulse value
+		_pulseAmount = 2;
+
+		if (Actions.isAgentRunning()) {
+			Actions.stopAgent();
+			return;
+		}
+
+		// if the last action was a delete, then ignore the next mouseup
+		if (_wasDouble) {
+			_wasDouble = false;
+			return;
+		}
+
+		/*
+		 * if (_isNoOp) { if (e.getButton() != MouseEvent.NOBUTTON) { _isNoOp =
+		 * false; _wasDouble = true; // lastRanged.clearSelection();
+		 * FrameGraphics.Repaint(); return; } }
+		 */
+
+		// get whatever the user was pointing at
+		ItemPermission clickedOn = _lastClickedOn;
+		List<ItemPermission> clickedIn = _lastClickedIn;
+
+		MouseX = e.getX();
+		MouseY = e.getY();
+
+		List<ItemPermission> releasedIn = FrameUtils.getCurrentItems();
+		ItemPermission releasedOn = FrameUtils.getCurrentItem();
+
+		if (_isNoOp
+				&& ((releasedOn != null && clickedOn != null && releasedOn.Item != clickedOn.Item) || (releasedOn == null && releasedIn == null))) {
+			if (_isDelete) {
+				_isDelete = false;
+				_wasDouble = true;
+			}
+
+			_isNoOp = false;
+
+			if (_lastHighlightedItem != null)
+				FrameGraphics.Highlight(_lastHighlightedItem, false, false);
+			// TODO Michael thinks this code should never be reached and can be
+			// removed
+			if (Frame.itemAttachedToCursor()) {
+				move(Frame.FreeItems);
+			}
+
+			FrameGraphics
+					.DisplayMessage("Action cancelled, mouse moved more than "
+							+ UserSettings.NoOpThreshold + " pixels.");
+			FrameGraphics.Repaint();
+			return;
+		} else
+			_isNoOp = false;
+
+		// if this is a delete command
+		if (_isDelete) {
+			if (lastRanged != null) {
+				lastRanged.cutSelectedText();
+				lastRanged.clearSelection();
+				FrameGraphics.Repaint();
+
+			} else {
+				delete(clickedOn);
+			}
+			_wasDouble = true;
+			_isDelete = false;
+			return;
+		}
+
+		// if this is an attribute extraction command
+		if (_isAttribute) {
+			extractAttributes(clickedOn);
+			_wasDouble = true;
+			_isAttribute = false;
+			return;
+		}
+
+		// if the user is ranging-out text
+		if (lastRanged != null) {
+
+			Text ranged = lastRanged.copy();
+
+			// if the user is cutting text from the item
+			if (e.getButton() == MouseEvent.BUTTON2) {
+				// if the entire text is selected then pickup the item
+				if (lastRanged.getSelectedSize() == lastRanged.getLength()) {
+					pickup(lastRanged);
+					lastRanged.clearSelection();
+					FrameGraphics.Repaint();
+					return;
+				}
+
+				ranged.setText(lastRanged.cutSelectedText());
+				// if the user is copying text from the item
+			} else if (e.getButton() == MouseEvent.BUTTON3)
+				ranged.setText(lastRanged.copySelectedText());
+
+			ranged.setLink(null);
+			ranged.setParent(null);
+			ranged.setPosition(DisplayIO.getMouseX(), DisplayIO.getMouseY());
+			pickup(ranged);
+
+			lastRanged.clearSelection();
+			FrameGraphics.Repaint();
+			return;
+		}
+
+		// if the user is cropping an imate
+		if (clickedOn != null && clickedOn.Item == _lastCropped
+				&& _lastCropped.getCroppedSize() > 0) {
+			Picture cropped = _lastCropped.copy();
+
+			cropped.setParent(null);
+			pickup(cropped);
+
+		}
+
+		// if the user has cropped an imate, either the above happend or this is
+		// a no-op
+		if (_lastCropped != null && _lastCropped.getCroppedSize() > 0) {
+			_lastCropped.clearCropping();
+			_lastCropped = null;
+			FrameGraphics.Repaint();
+			return;
+		}
+
+		// if the user is left-clicking
+		if (e.getButton() == MouseEvent.BUTTON1) {
+			SessionStats.AddFrameEvent("Lu");
+			leftButton(clickedOn);
+			return;
+		}
+
+		if (e.getButton() == MouseEvent.BUTTON2) {
+			SessionStats.AddFrameEvent("Mu");
+			middleButton(clickedOn, clickedIn);
+			return;
+		}
+
+		if (e.getButton() == MouseEvent.BUTTON3) {
+			SessionStats.AddFrameEvent("Ru");
+			rightButton(clickedOn, clickedIn);
+			return;
+		}
+
+		// error, we should have returned by now
+		System.out.println("Error: mouseReleased should have returned by now. "
+				+ e);
+	}
+
+	/**
+	 * This method handles all left-click actions
+	 */
+	public static void leftButton(ItemPermission clicked) {
+		// if the user is pointing at something then either follow the link or
+		// do TDFC
+		if (clicked != null) {
+			// check item permissions
+			if (clicked.Permission < ItemPermission.PERMISSION_FOLLOW_LINKS) {
+				FrameGraphics
+						.DisplayMessage("Insufficient Permissions for action on item");
+				return;
+			}
+
+			Item clickedOn = clicked.Item;
+			// actions take priority
+			if (_lastMouseClick != null && !_lastMouseClick.isShiftDown()
+					&& clickedOn.getAction() != null) {
+				clickedOn.performActions();
+				return;
+			} else if (clickedOn.getLink() != null) {
+				// Dont save the frame if we are moving to an old version of
+				// this frame
+				// because everytime we save with the old tag... the frame is
+				// backed up
+				if (!clickedOn.isOldTag())
+					FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
+
+				NavigationActions.setLastNavigationItem(clickedOn);
+				load(clickedOn.getAbsoluteLink());
+				// DisplayIO.UpdateTitle();
+				return;
+				// no link is found, perform TDFC
+			} else {
+				// check for TDFC permission
+				if (clicked.Permission < ItemPermission.PERMISSION_TDFC) {
+					FrameGraphics
+							.DisplayMessage("Insufficient permission to TDFC from that item");
+					return;
+				}
+
+				if (clickedOn.isOldTag())
+					return;
+
+				// if the user is clicking on the frame name
+				if (clickedOn.isFrameName()) {
+					Frame next = FrameIO.LoadNext();
+					FrameUtils.DisplayFrame(next, true);
+					return;
+				}
+
+				tdfc(clickedOn);
+				DisplayIO.UpdateTitle();
+				return;
+			}
+			// if the user is not pointing at something, then this is a back
+			// command
+		} else {
+			back();
+			DisplayIO.UpdateTitle();
+			return;
+		}
+	}
+
+	private static boolean doMerging(ItemPermission clicked) {
+		if (clicked == null)
+			return false;
+
+		// System.out.println(Frame.FreeItems.size());
+		if (isRubberBandingRectangle()
+				&& clicked.Item.getAllConnected().contains(
+						Frame.getItemAttachedToCursor()))
+			return true;
+
+		if (Frame.FreeItems.size() > 2)
+			return false;
+
+		Item attachedToCursor = Frame.getItemAttachedToCursor();
+
+		if (clicked.Item instanceof Text && !(attachedToCursor instanceof Text)) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * This method handles all middle-click actions
+	 */
+	private static void middleButton(ItemPermission clicked,
+			List<ItemPermission> clickedIn) {
+		// if the cursor has Items attached
+		if (Frame.itemAttachedToCursor()) {
+			// if the user is pointing at something, merge the items (if
+			// possible)
+			if (doMerging(clicked)) {
+				// check permissions
+				if (clicked.Permission < ItemPermission.PERMISSION_FULL
+						&& clicked.Item.getParent() != null
+						&& clicked.Item.getParent().getName() != clicked.Item) {
+					FrameGraphics.DisplayMessage("Insufficient Permission");
+					return;
+				}
+
+				List<Item> left = merge(Frame.FreeItems, clicked.Item);
+				anchor(left);
+
+				Frame.FreeItems.clear();
+				FrameGraphics.Repaint();
+				updateCursor();
+				return;
+				// otherwise, anchor the items
+			} else {
+				// if a line is being rubber-banded, check for auto
+				// straightening
+				anchor(Frame.FreeItems);
+				Frame.FreeItems.clear();
+				updateCursor();
+				_offX = _offY = 0;
+				return;
+			}
+		} else {
+			// otherwise if the user is pointing at something,pick it up
+			if (clicked != null) {
+				// check permissions
+				if (clicked.Permission < ItemPermission.PERMISSION_FULL) {
+					FrameGraphics
+							.DisplayMessage("Insufficient Permission to pick up item");
+					return;
+				}
+
+				pickup(clicked.Item);
+				return;
+				// otherwise create a line
+			} else {
+				if (clickedIn != null) {
+					ArrayList<Item> toPickup = new ArrayList<Item>(clickedIn
+							.size());
+					for (ItemPermission ip : clickedIn)
+						if (ip.Permission >= ItemPermission.PERMISSION_FULL)
+							toPickup.add(ip.Item);
+
+					pickup(toPickup);
+					// otherwise the user is creating a line
+				} else
+					createLine();
+				return;
+			}
+		}
+	}
+
+	private static Dot getFirstFreeDot() {
+		for (Item i : Frame.FreeItems)
+			if (i instanceof Dot)
+				return (Dot) i;
+		return null;
+	}
+
+	private static boolean isRubberBandingRectangle() {
+		if (Frame.FreeItems.size() != 3)
+			return false;
+		Dot d = null;
+		// only one dot will be present for rectangles
+		for (Item i : Frame.FreeItems)
+			if (i instanceof Dot && d == null)
+				d = (Dot) i;
+			else if (i instanceof Dot && d != null)
+				d = null;
+
+		// System.out.println(d.getAllConnected());
+
+		// if this is indeed a rectangle anchor
+		if (d != null && d.getAllConnected().size() > 5)
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * This method handles all right-click action
+	 */
+	private static void rightButton(ItemPermission clicked,
+			List<ItemPermission> clickedIn) {
+		// if the cursor has Items attached, then anchor a copy of them
+		if (Frame.itemAttachedToCursor()) {
+			// if the user is clicking on something, merge the items
+			// unless it is a point onto somethin other than a point
+			if (clicked != null
+					&& (!(Frame.getItemAttachedToCursor() instanceof Dot && !(clicked.Item instanceof Dot)))) {
+				// check permissions
+				if (clicked.Permission < ItemPermission.PERMISSION_FULL
+						&& clicked.Item.getParent().getName() != clicked.Item) {
+					FrameGraphics
+							.DisplayMessage("Insufficient Permission to merge items");
+					return;
+				}
+
+				if (clicked.Item instanceof Text || clicked.Item instanceof Dot) {
+					List<Item> copies = null;
+
+					if (isRubberBandingRectangle()) {
+						List<Item> remain = merge(Frame.FreeItems, clicked.Item);
+
+						Dot d = getFirstFreeDot();
+						// anchor the points
+						anchor(remain);
+						Frame.FreeItems.clear();
+						updateCursor();
+						// pick up a copy of all enclosed items
+						List<ItemPermission> items = FrameUtils
+								.getItemsEnclosedBy(
+										DisplayIO.getCurrentFrame(), d
+												.getEnclosedShape());
+						if (items != null) {
+							ArrayList<Item> toCopy = new ArrayList<Item>(items
+									.size());
+							for (ItemPermission ip : items)
+								if (ip.Permission >= ItemPermission.PERMISSION_COPY)
+									toCopy.add(ip.Item);
+
+							copies = copy(toCopy);
+							pickup(copies);
+						}
+
+					} else if (Frame.FreeItems.size() == 2) {
+						copies = ItemUtils.UnreelLine(Frame.FreeItems);
+						List<Item> leftOver = merge(Frame.FreeItems,
+								clicked.Item);
+						anchor(leftOver);
+						if (copies == null)
+							copies = copy(Frame.FreeItems);
+						Frame.FreeItems.clear();
+						for (Item i : copies)
+							i.setOffset(0, 0);
+						// need to move to prevent cursor dislocation
+						move(copies);
+						pickup(copies);
+
+					} else if (Frame.FreeItems.size() == 1) {
+						copies = copy(Frame.FreeItems);
+						List<Item> remain = merge(copies, clicked.Item);
+
+						// ignore items that could not be merged.
+						anchor(remain);
+					} else {
+						// NoOp
+					}
+					updateCursor();
+					FrameGraphics.Repaint();
+				} else {
+					List<Item> copies = ItemUtils.UnreelLine(Frame.FreeItems);
+					if (copies == null)
+						copies = copy(Frame.FreeItems);
+					for (Item i : copies) {
+						i.setOffset(0, 0);
+					}
+					anchor(Frame.FreeItems);
+					Frame.FreeItems.clear();
+					updateCursor();
+
+					FrameGraphics.Repaint();
+
+					clearParent(copies);
+					pickup(copies);
+				}
+				return;
+
+				// otherwise, anchor the items
+			} else {
+
+				// check if this is anchoring a rectangle
+				if (isRubberBandingRectangle()) {
+					Dot d = getFirstFreeDot();
+
+					// anchor the points
+					anchor(Frame.FreeItems);
+					Frame.FreeItems.clear();
+					updateCursor();
+					// pick up a copy of all enclosed items
+					List<ItemPermission> items = FrameUtils.getItemsEnclosedBy(
+							DisplayIO.getCurrentFrame(), d.getEnclosedShape());
+					if (items != null) {
+						ArrayList<Item> toCopy = new ArrayList<Item>(items
+								.size());
+						for (ItemPermission ip : items)
+							if (ip.Permission >= ItemPermission.PERMISSION_COPY)
+								toCopy.add(ip.Item);
+						toCopy.removeAll(d.getAllConnected());
+
+						if (toCopy.size() > 0) {
+							pickup(copy(toCopy));
+							DisplayIO.getCurrentFrame().removeAllItems(d.getAllConnected());
+						} else {
+							pickup(copy(d.getAllConnected()));
+						}
+					}
+					return;
+				}
+				List<Item> copies = null;
+				if (Frame.FreeItems.size() == 2) {
+					if (clicked != null) {
+						List<Item> leftOver = merge(Frame.FreeItems,
+								clicked.Item);
+						anchor(leftOver);
+					}
+					// This is executed when the user is putting down a line
+					// endpoint and unreeling. ie. Normal unreeling
+					copies = ItemUtils.UnreelLine(Frame.FreeItems);
+
+					if (copies == null)
+						copies = copy(Frame.FreeItems);
+					anchor(Frame.FreeItems);
+					for (Item i : copies)
+						i.setOffset(0, 0);
+					// need to move to prevent cursor dislocation
+					move(copies);
+					pickup(copies);
+					FrameGraphics.Repaint();
+					return;
+				}
+
+				if (copies == null)
+					copies = copy(Frame.FreeItems);
+				// MIKE: what does the below 2 lines do?
+				for (Item i : copies)
+					i.setOffset(0, 0);
+				// The below code has a little problem withflicker when stamp
+				// and dragging
+				move(Frame.FreeItems);
+				anchor(copies);
+				updateCursor();
+				FrameGraphics.Repaint();
+				return;
+			}
+
+		} else {
+			// if the user is pointing at something, this is a copy
+			if (clicked != null) {
+				// check permissions
+				if (clicked.Permission < ItemPermission.PERMISSION_COPY) {
+					FrameGraphics
+							.DisplayMessage("Insufficient permission to copy item");
+					return;
+				}
+
+				// This happens when the user right clicks on the endpoint of an
+				// existing line
+				// check for unreeling
+				List<Item> copies = ItemUtils.UnreelLine(clicked.Item);
+
+				if (copies == null) {
+					// check if this is picking up a rectangle
+					if (clicked.Item instanceof Dot
+							&& clicked.Item.getConnected().size() == 3) {
+						Dot d = null;
+						// only one dot will be present for rectangles
+						for (Item i : clicked.Item.getConnected())
+							if (i instanceof Dot && d == null)
+								d = (Dot) i;
+							else if (i instanceof Dot && d != null)
+								d = null;
+
+						// if this is indeed a rectangle anchor
+						if (d != null) {
+							// pick up a copy of all enclosed items
+							List<ItemPermission> items = FrameUtils
+									.getItemsEnclosedBy(DisplayIO
+											.getCurrentFrame(), d
+											.getEnclosedShape());
+							if (items != null) {
+								ArrayList<Item> toCopy = new ArrayList<Item>(
+										items.size());
+								for (ItemPermission ip : items)
+									if (ip.Permission >= ItemPermission.PERMISSION_COPY)
+										toCopy.add(ip.Item);
+
+								List<Item> toPickup = copy(toCopy);
+								clearParent(toPickup);
+								pickup(toPickup);
+							}
+							return;
+						}
+					}
+					copies = copy(clicked.Item);
+				}
+
+				pickup(copies);
+				return;
+
+			} else {
+				// if the user is pointing inside a closed shape, copy the items
+				// inside
+				if (clickedIn != null) {
+					List<Item> toCopy = new ArrayList<Item>(clickedIn.size());
+					for (ItemPermission ip : clickedIn)
+						if (ip.Permission >= ItemPermission.PERMISSION_COPY)
+							toCopy.add(ip.Item);
+
+					List<Item> copies = copy(toCopy);
+					clearParent(copies);
+					pickup(copies);
+					// otherwise, create a rectangle
+				} else {
+
+					int x = DisplayIO.getMouseX();
+					int y = DisplayIO.getMouseY();
+
+					// create dots
+					Dot d1 = new Dot(x, y, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+					Dot d2 = new Dot(x, y, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+					Dot d3 = new Dot(x, y, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+					Dot d4 = new Dot(x, y, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+
+					// create lines
+					Line l1 = new Line(d1, d2, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+					Line l2 = new Line(d2, d3, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+					Line l3 = new Line(d3, d4, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+					Line l4 = new Line(d4, d1, DisplayIO.getCurrentFrame()
+							.getNextItemID());
+
+					new Constraint(d1, d2, DisplayIO.getCurrentFrame()
+							.getNextItemID(), Constraint.HORIZONTAL);
+					new Constraint(d3, d4, DisplayIO.getCurrentFrame()
+							.getNextItemID(), Constraint.HORIZONTAL);
+					new Constraint(d2, d3, DisplayIO.getCurrentFrame()
+							.getNextItemID(), Constraint.VERTICAL);
+					new Constraint(d4, d1, DisplayIO.getCurrentFrame()
+							.getNextItemID(), Constraint.VERTICAL);
+
+					anchor(d1);
+					anchor(d2);
+					anchor(d3);
+					anchor(d4);
+
+					anchor(l1);
+					anchor(l2);
+					anchor(l3);
+					anchor(l4);
+
+					pickup(d4);
+
+					FrameGraphics.Repaint();
+					return;
+				}
+			}
+		}
+	}
+
+	private static void clearParent(List<Item> items) {
+		for (Item i : items)
+			i.setParent(null);
+	}
+
+	public void mouseEntered(MouseEvent e) {
+	}
+
+	public void mouseExited(MouseEvent e) {
+	}
+
+	public void mouseDragged(MouseEvent e) {
+		// System.out.println("MouseDragged");
+
+		// check if user is dragging across a text item
+		if (_lastRanged != null) {
+			// System.out.println(MouseY - e.getY());
+
+			MouseX = e.getX();
+			MouseY = e.getY();
+
+			int distance = _lastRanged.getY() - DisplayIO.getMouseY();
+			if (distance <= 0)
+				distance = DisplayIO.getMouseY() - _lastRanged.getY()
+						- _lastRanged.getBoundsHeight();
+
+			if (distance > UserSettings.NoOpThreshold) {
+				_lastRanged.clearSelectionEnd();
+				_isNoOp = true;
+			} else {
+				// update the ranged section
+				_lastRanged.setSelectionEnd(DisplayIO.getMouseX(), DisplayIO
+						.getMouseY());
+				_isNoOp = false;
+			}
+
+			DisplayIO.setTextCursor(_lastRanged.getSize());
+			FrameGraphics.Repaint();
+			return;
+		}
+
+		// if the user is dragging across a picture
+		if (_lastCropped != null) {
+			MouseX = e.getX();
+			MouseY = e.getY();
+
+			// update the ranged section
+			_lastCropped.setEndCrop(DisplayIO.getMouseX(), DisplayIO
+					.getMouseY());
+			DisplayIO.setCursor(Item.CROP_CURSOR);
+			FrameGraphics.Repaint();
+			return;
+		}
+
+		/*
+		 * // Euclidean distance int distance = (int)
+		 * Math.sqrt(Math.pow(Math.abs(MouseX - e.getX()), 2) +
+		 * Math.pow(Math.abs(MouseY - e.getY()), 2));
+		 * 
+		 * if (distance > UserSettings.NoOpThreshold)
+		 */
+		// Use the below calculation for better speed. If it causes problems
+		// switch back to the Euclidean distance calculation
+		if (Math.abs(MouseX - e.getX()) > UserSettings.NoOpThreshold
+				|| Math.abs(MouseY - e.getY()) > UserSettings.NoOpThreshold)
+			_isNoOp = true;
+	}
+
+	MouseEvent _lastMouseEvent = null;
+
+	/**
+	 * Updates the stored mouse position and highlights any items as necessary.
+	 */
+	public void mouseMoved(MouseEvent e) {
+		// System.out.println("MouseMoved");
+
+		MouseX = e.getX();
+		MouseY = e.getY();
+
+		if (_lastMouseEvent == null)
+			_lastMouseEvent = e;
+
+		/*
+		 * int distance = (int) Math.sqrt(Math.pow(Math
+		 * .abs(_lastMouseEvent.getX() - e.getX()), 2) +
+		 * Math.pow(Math.abs(_lastMouseEvent.getY() - e.getY()), 2));
+		 */
+
+		// Just do the sum of the distance moved in x and y direction for
+		// speed!!
+		int distance = Math.abs(_lastMouseEvent.getX() - e.getX())
+				+ Math.abs(_lastMouseEvent.getY() - e.getY());
+
+		boolean checkHighlight = true;
+
+		// Mike: This code checks if the mouse is moving above a
+		// threshold speed. If so it doesnt execute the code that checks if
+		// items underneath the cursor need to be highlighted.
+		if (Frame.itemAttachedToCursor()) {
+			long time = e.getWhen() - _lastMouseEvent.getWhen();
+			// System.out.println(time + " D:" + distance);
+			if (time > 0 && distance * 3 > time) {
+				checkHighlight = false;
+				// System.out.println("Dont check highlight!");
+				if (_lastHighlightedItem != null && !_lastHoldsHighlight) {
+					FrameGraphics.Highlight(_lastHighlightedItem, false, true);
+				}
+			}
+		}
+
+		_lastMouseEvent = e;
+
+		if (checkHighlight) {
+			// ByMike: Get the item the mouse is hovering over
+			ItemPermission click = FrameUtils.getCurrentItem();
+			Item on = null;
+
+			if (click != null) {
+				on = click.Item;
+				// set the context
+				if (on instanceof Line)
+					_context = CONTEXT_AT_LINE;
+				else if (on instanceof Dot)
+					_context = CONTEXT_AT_DOT;
+				else if (on instanceof Text) {
+					// Checks that we are actually pointing on a character
+					// not just space in the text box's bounding box
+					if (((Text) on).contains(DisplayIO.getMouseX(), DisplayIO
+							.getMouseY())) {
+						_context = CONTEXT_AT_TEXT;
+					} else {
+						// if we are pointing in space in the text box... it is
+						// the same as pointing in freespace
+						on = null;
+						_context = CONTEXT_FREESPACE;
+					}
+				}
+
+				_alpha = 60;
+			} else {
+				_context = CONTEXT_FREESPACE;
+				_alpha = -1;
+			}
+
+			// if the user is pointing at an item, highlight it
+			if (on != null && !Frame.FreeItems.contains(on)) {
+				// if the user can spot-weld, show the virtual spot
+				if (Frame.FreeItems.size() == 2 && on instanceof Line) {
+					Line line = (Line) on;
+					if (Frame.FreeItems.get(0) instanceof Dot)
+						line.showVirtualSpot((Dot) Frame.FreeItems.get(0),
+								DisplayIO.getMouseX(), DisplayIO.getMouseY());
+					else if (Frame.FreeItems.get(1) instanceof Dot)
+						line.showVirtualSpot((Dot) Frame.FreeItems.get(1),
+								DisplayIO.getMouseX(), DisplayIO.getMouseY());
+					else
+						FrameGraphics.Highlight(on, true, true);
+				} else
+					FrameGraphics.Highlight(on, true, true);
+
+				// if the last item highlighted is still highlighted, clear it
+				if (_lastHoldsHighlight) {
+					_lastHoldsHighlight = false;
+					for (Item i : DisplayIO.getCurrentFrame().getItems())
+						if (i.isHighlighted() && i != on)
+							FrameGraphics.Highlight(i, false, false);
+				}
+
+				// if the user is not pointing at an item, check for enclosure
+				// highlighting
+			} else if (on == null) {
+				List<Dot> enclosure = FrameUtils.getEnclosingDots();
+				if (enclosure != null && enclosure.size() > 1) {
+					if (enclosure.get(0).getLines().size() > 1) {
+						on = enclosure.get(0).getLines().get(0);
+						// FrameGraphics.Highlight(enclosure.get(0).getLines().get(0),
+						// true, true);
+						FrameGraphics.Highlight(on, true, true);
+					}
+					// for(Dot d : enclosure){
+					// FrameGraphics.Highlight(d, true, true);
+					// break;
+					// }
+				} else if (_lastHighlightedItem != null) {
+					_lastHoldsHighlight = false;
+				}
+			}
+
+			// disable cursor changes when the cursor has items attached
+			if (Frame.itemAttachedToCursor()
+					&& DisplayIO.getCursor() != Item.TEXT_CURSOR)
+				_forceArrowCursor = false;
+
+			if (_lastHighlightedItem != null && _lastHighlightedItem != on
+					&& !_lastHoldsHighlight) {
+				FrameGraphics.Highlight(_lastHighlightedItem, false, true);
+			}
+
+			_lastHighlightedItem = on;
+
+		}
+
+		if (Frame.itemAttachedToCursor()) {
+			move(Frame.FreeItems);
+		}
+
+		if (_forceArrowCursor)
+			updateCursor();
+
+		// if (_lastItem == null)
+		// DisplayIO.UpdateTitle();
+
+		_forceArrowCursor = true;
+	}
+
+	/**
+	 * Updates the current mouse cursor to whatever it should be. i.e. Hidden
+	 * when rubber-banding lines, otherwise default (arrow)
+	 */
+	private static void updateCursor() {
+		// if rubber-banding, there will be exactly 2 items on the cursor
+		if (Frame.FreeItems.size() != 2)
+			DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+		else {
+			Dot d = null;
+			// check that it is indeed a line being rubber-banded
+			if (Frame.FreeItems.get(0) instanceof Line
+					&& Frame.FreeItems.get(1) instanceof Dot)
+				d = (Dot) Frame.FreeItems.get(1);
+			else if (Frame.FreeItems.get(1) instanceof Line
+					&& Frame.FreeItems.get(0) instanceof Dot)
+				d = (Dot) Frame.FreeItems.get(0);
+
+			if (d != null) {
+				DisplayIO.setCursor(Item.HIDDEN_CURSOR);
+			} else
+				DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+		}
+
+	}
+
+	public static void setHighlightHold(boolean hold) {
+		_lastHoldsHighlight = hold;
+	}
+
+	public static void resetOffset() {
+		if (Frame.itemAttachedToCursor()) {
+			_offX = DisplayIO.getMouseX() - Frame.FreeItems.get(0).getX()
+					+ Frame.FreeItems.get(0).getOffset().x;
+			_offY = MouseY - Frame.FreeItems.get(0).getY()
+					+ Frame.FreeItems.get(0).getOffset().y;
+		}
+	}
+
+	private static void move(List<Item> toMove) {
+		int deltax = toMove.get(0).getX() - (DisplayIO.getMouseX() - _offX);
+		int deltay = toMove.get(0).getY() - (MouseY - _offY);
+
+		for (Item move : toMove) {
+			move.setPosition(move.getX() - deltax, move.getY() - deltay);
+
+			if (move instanceof Text)
+				((Text) move).setAlpha(_alpha);
+		}
+
+		FrameGraphics.Repaint();
+	}
+
+	private static void load(String toLoad) {
+		checkTDFCItemWaiting();
+		FrameUtils.DisplayFrame(toLoad);
+	}
+
+	private static void checkTDFCItemWaiting() {
+		// if there is a TDFC Item waiting
+		if (_tdfcItem != null) {
+			Frame currentFrame = DisplayIO.getCurrentFrame();
+			boolean change = currentFrame.hasChanged();
+			boolean saved = currentFrame.isSaved();
+			// Save the parent of the item if it has not been saved
+			// TODO change it so it doesnt use load frame here... if
+			// possible
+			if (!change && !saved) {
+				_tdfcItem.setLink(null);
+				_tdfcItem.getParent().setChanged(true);
+				FrameIO.SaveFrame(_tdfcItem.getParent());
+				FrameGraphics.Repaint();
+			} else {
+				SessionStats.CreatedFrame();
+				// DisplayIO.UpdateTitle();
+			}
+
+			_tdfcItem = null;
+		}
+	}
+
+	private static void back() {
+		checkTDFCItemWaiting();
+		DisplayIO.Back();
+
+		// repaint things if necessary
+		if (Frame.itemAttachedToCursor())
+			move(Frame.FreeItems);
+	}
+
+	/**
+	 * Returns true if the mouse moved during TDFC. This will happen if there is
+	 * a start annotation item on the frame.
+	 * 
+	 * @param linker
+	 * @return
+	 */
+	public static boolean tdfc(Item linker) {
+		if (linker instanceof Line)
+			return false;
+
+		// if this is a non-usable item
+		if (linker.getID() < 0)
+			return false;
+
+		linker.getParent().setChanged(true);
+		checkTDFCItemWaiting();
+
+		Frame next = FrameIO.CreateNewFrame(linker);
+
+		linker.setLink("" + next.getFrameNumber());
+		_tdfcItem = linker;
+
+		FrameUtils.DisplayFrame(next, true);
+
+		boolean mouseMoved = next.moveMouseToDefaultLocation();
+		// this needs to be done if the user doesnt move the mouse before doing
+		// tdfc while the cursor is set to the text cursor
+		DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+		// This needs to be done in case there was a @start on the frame which
+		// triggers changed to be set to true when it should stay as false
+		next.setChanged(false);
+
+		return mouseMoved;
+	}
+
+	/**
+	 * Creates a new Text item and fills it with particular attributes extracted
+	 * from the given Item. Note: Users always have permission to extract
+	 * attributes, so it is not checked.
+	 * 
+	 * @param toExtract
+	 *            ItemPermission containing the Item to extract the attributes
+	 *            from.
+	 */
+	private static void extractAttributes(ItemPermission toExtract) {
+		if (toExtract == null || toExtract.Item == null)
+			return;
+
+		if (Frame.itemAttachedToCursor())
+			return;
+
+		Text attribs;
+		Item item = toExtract.Item;
+		// Extract the frames attributes when the user clicks on the frame name
+		if (item.isFrameName())
+			attribs = AttributeUtils.ExtractAttributes(item.getParent());
+		else
+			attribs = AttributeUtils.ExtractAttributes(item);
+
+		if (attribs == null)
+			FrameGraphics
+					.DisplayMessage("All attributes of that item are default values.");
+		else {
+			// Give the attribute text item the color of the item for which
+			// attributes are being extracted.
+			attribs.setColor(item.getColor());
+			pickup(attribs);
+		}
+	}
+
+	private static void delete(ItemPermission toDelete) {
+		FrameUtils.LastEdited = null;
+		_offX = _offY = 0;
+
+		// check if the user is pointing at the frame's framename
+		if (toDelete != null
+				&& toDelete.Item == DisplayIO.getCurrentFrame().getName()) {
+			DisplayIO.getCurrentFrame().clear();
+			FrameGraphics.Repaint();
+			return;
+		}
+
+		// if the user is deleting items attached to the cursor
+		if (Frame.itemAttachedToCursor()) {
+			// if this is an item-swap
+			if (Frame.FreeItems.size() == 1
+					&& Frame.FreeItems.get(0) instanceof Text
+					&& toDelete != null && toDelete.Item instanceof Text) {
+
+				// check permissions
+				if (toDelete.Permission < ItemPermission.PERMISSION_FULL) {
+					FrameGraphics
+							.DisplayMessage("Insufficient permission to swap Item text");
+					return;
+				}
+
+				Text anchored = (Text) toDelete.Item;
+				Text free = (Text) Frame.FreeItems.get(0);
+
+				// List<String> temp = anchored.getText();
+				anchored.setTextList(free.getText());
+
+				// free.setTextList(temp);
+				Frame.FreeItems.clear();
+
+				anchored.getParent().setChanged(true);
+
+				// update the offset since the text has changed
+				_offX = DisplayIO.getMouseX() - anchored.getX()
+						+ anchored.getOffset().x;
+				_offY = MouseY - anchored.getY() + anchored.getOffset().y;
+			} else {
+				deleteItems(Frame.FreeItems);
+			}
+			// reset the mouse cursor
+			updateCursor();
+			FrameGraphics.Repaint();
+			return;
+			// the user is not pointing at an item
+		} else if (toDelete == null) {
+
+			// if the user is pointing inside a closed shape, delete all
+			// items inside it
+			List<ItemPermission> items = FrameUtils.getCurrentItems();
+
+			if (items != null) {
+				ArrayList<Item> toRemove = new ArrayList<Item>(items.size());
+				for (ItemPermission ip : items)
+					if (ip.Permission >= ItemPermission.PERMISSION_FULL) {
+						toRemove.add(ip.Item);
+					}
+
+				deleteItems(toRemove);
+
+				// reset the mouse cursor
+				updateCursor();
+				FrameGraphics.Repaint();
+
+				// otherwise this is an undo command
+			} else {
+				DisplayIO.getCurrentFrame().undo();
+			}
+			return;
+			// this is a delete command
+		} else {
+			// check permissions
+			if (toDelete.Permission < ItemPermission.PERMISSION_FULL) {
+				FrameGraphics
+						.DisplayMessage("Insufficient permission to delete item");
+				return;
+			}
+
+			Frame current = toDelete.Item.getParent();
+
+			current.setChanged(true);
+
+			List<Item> toUndo = new LinkedList<Item>();
+
+			if (toDelete.Item instanceof Dot) {
+				toUndo.addAll(deleteDot((Dot) toDelete.Item));
+			} else
+				toUndo.addAll(copy(toDelete.Item.getConnected()));
+
+			current.addAllToUndo(toUndo);
+			current.removeAllItems(toDelete.Item.getConnected());
+			// reset the mouse cursor
+			updateCursor();
+			FrameGraphics.Repaint();
+
+			ItemUtils.EnclosedCheck(current.getItems());
+			FrameGraphics.Repaint();
+		}
+		// check
+		DisplayIO.setCursor(Item.DEFAULT_CURSOR);
+	}
+
+	private static void deleteItems(List<Item> itemList) {
+		Frame current = DisplayIO.getCurrentFrame();
+		List<Item> toUndo = new LinkedList<Item>();
+		List<Item> seen = new LinkedList<Item>();
+
+		// disconnect any connected items
+		for (Item i : itemList) {
+			if (i.getLines().size() > 0) {
+
+				// check for rectangle deletion
+				if (itemList.size() == 3) {
+					boolean rectangle = false;
+					// check that this is indeed a rectangle
+					for (Item check : itemList)
+						if (check != i && !i.getLines().contains(check)) {
+							rectangle = false;
+							break;
+						} else
+							rectangle = true;
+
+					// if this is a rectangle, remove the entire
+					// rectangle
+					if (rectangle) {
+						List<Item> copy = copy(i.getAllConnected());
+						current.addAllToUndo(copy);
+						current.removeAllItems(i.getAllConnected());
+						itemList.clear();
+						// reset the mouse cursor
+						updateCursor();
+						FrameGraphics.Repaint();
+						return;
+					}
+				}
+
+				List<Item> copy = deleteDot((Dot) i);
+				// add the copied items to the undo stack
+				for (Item cop : copy)
+					if (!toUndo.contains(cop))
+						toUndo.add(cop);
+			} else {
+				if (!seen.contains(i))
+					toUndo.addAll(copy(i));
+			}
+
+			seen.add(i);
+		}
+
+		// check for rectangle deletion
+		if (itemList.size() == 3) {
+			boolean rectangle = false;
+			Dot dot = null;
+			// check that this is indeed a rectangle
+			for (Item check : itemList) {
+				if (dot == null && check instanceof Dot)
+					dot = (Dot) check;
+				else if (check != dot && !dot.getLines().contains(check)) {
+					rectangle = false;
+					break;
+				} else
+					rectangle = true;
+			}
+
+			// if this is a rectangle, remove the entire rectangle
+			if (rectangle) {
+				// List<Item> copy = copy(dot.getAllConnected());
+				current.addAllToUndo(dot.getAllConnected());
+				current.removeAllItems(dot.getAllConnected());
+				itemList.clear();
+				// reset the mouse cursor
+				updateCursor();
+				FrameGraphics.Repaint();
+				return;
+			}
+		}
+
+		current.removeAllItems(itemList);
+		current.addAllToUndo(itemList);
+		itemList.clear();
+	}
+
+	private static List<Item> deleteDot(Dot dot) {
+		// create a backup copy of the dot
+		List<Item> copy = copy(dot.getConnected());
+		// remove lines that are connected to anchored dots
+		// note: the line is kept so that it can be properly restored
+		for (Item ic : copy) {
+			if (ic instanceof Line) {
+				Line line = (Line) ic;
+				if (!copy.contains(line.getStartItem()))
+					line.getStartItem().removeLine(line);
+				if (!copy.contains(line.getEndItem()))
+					line.getEndItem().removeLine(line);
+			}
+		}
+
+		// remove all lines being deleted
+		for (Item ic : dot.getConnected()) {
+			if (ic instanceof Line && ((Line) ic).getOppositeEnd(dot) != null) {
+				Line line = (Line) ic;
+
+				Item d = line.getOppositeEnd(dot);
+				d.removeLine(line);
+
+				// if the dot was only part of one line, it can be
+				// removed
+				if (d.getLines().size() == 0) {
+					if (d.getParent() != null)
+						d.getParent().removeItem(d);
+					if (!copy.contains(d))
+						copy.add(d);
+				}
+
+				if (dot.getParent() != null)
+					dot.getParent().removeItem(ic);
+			}
+		}
+		return copy;
+	}
+
+	private static void removeAllLinesExcept(Item from, Item but) {
+		List<Line> lines = new LinkedList<Line>();
+		lines.addAll(from.getLines());
+		for (Line line : lines)
+			if (line.getOppositeEnd(from) != but)
+				from.removeLine(line);
+
+		List<Constraint> consts = new LinkedList<Constraint>();
+		consts.addAll(from.getConstraints());
+		for (Constraint c : consts)
+			if (c.getOppositeEnd(from) != but)
+				from.removeConstraint(c);
+	}
+
+	public static List<Item> merge(List<Item> merger, Item mergee) {
+		if (mergee.getParent().getName() == mergee) {
+			return mergee.getParent().merge(merger);
+		}
+
+		// check for rectangle merging
+		if (merger.size() == 3 && mergee.getLines().size() == 2) {
+			boolean rectangle = false;
+			Dot dot = null;
+			// check that this is indeed a rectangle
+			for (Item check : merger) {
+				if (dot == null && check instanceof Dot)
+					dot = (Dot) check;
+				else if (check != dot && !dot.getLines().contains(check)) {
+					rectangle = false;
+					break;
+				} else
+					rectangle = true;
+			}
+
+			// if this is a rectangle
+			if (rectangle) {
+				List<Item> connected = dot.getAllConnected();
+				DisplayIO.getCurrentFrame().removeAllItems(connected);
+
+				// find the point opposite the point that was clicked
+				// (mergee)...
+				Dot opposite = null;
+				List<Line> lines = dot.getLines();
+				for (Line l : lines) {
+					if (l.getOppositeEnd(dot) != mergee) {
+						opposite = (Dot) l.getOppositeEnd(dot);
+						break;
+					}
+				}
+
+				// check if the rectangle is small enough that it should be
+				// collapsed to a single point
+				int x1 = Math.abs(opposite.getX() - mergee.getX());
+				int x2 = Math.abs(opposite.getY() - mergee.getY());
+				int distance = (int) Math.sqrt(Math.pow(x1, 2)
+						+ Math.pow(x2, 2));
+
+				if (distance < RECTANGLE_TO_POINT_THRESHOLD) {
+					mergee.removeAllConstraints();
+					mergee.removeAllLines();
+					return mergee.getAllConnected();
+				} else {
+
+					// otherwise, replace the rectangle with a constrained
+					// line
+					Item other = null;
+					// there should be only one line left
+					for (Line line : mergee.getLines())
+						if (!merger.contains(line.getOppositeEnd(mergee)))
+							other = line.getOppositeEnd(mergee);
+
+					removeAllLinesExcept(mergee, other);
+					removeAllLinesExcept(other, mergee);
+
+					return mergee.getAllConnected();
+				}
+			}
+		}
+
+		List<Item> remain = new ArrayList<Item>();
+		Item res = null;
+
+		for (Item i : merger) {
+			// check for link merging
+			if (i instanceof Text
+					&& FrameIO.isValidFrameName((((Text) i).getFirstLine()))
+					&& FrameIO.DoesFrameExist((((Text) i).getFirstLine()))) {
+				// check that we can actually access the frame this link
+				// corresponds to
+
+				mergee.setLink(((Text) i).getFirstLine());
+			} else {
+				// check for attribute merging
+				if (i instanceof Text) {
+					Text txt = (Text) i;
+
+					// if this is not an attribute merge
+					if (!AttributeUtils.SetAttribute(mergee, txt)) {
+						// set mouse position for text merges
+						if (mergee instanceof Text)
+							// 18Feb Rob doesnt want cursor to jump when
+							// inserting text
+							((Text) mergee).insertText(txt.getTextNoList(),
+									DisplayIO.getMouseX(), DisplayIO
+											.getMouseY());
+
+						else if (mergee instanceof Dot) {
+							mergee.getParent().removeItem(mergee);
+							break;
+						}
+
+						// TODO tidy this up... the text override of this method
+						// doesnt use the parametres MouseY and MouseX!!
+						res = mergee.merge(i, DisplayIO.getMouseX(), MouseY
+								- DisplayIO.getOffset());
+						if (res != null) {
+							remain.add(res);
+						}
+					}
+				} else {
+					res = mergee.merge(i, DisplayIO.getMouseY(), MouseY
+							- DisplayIO.getOffset());
+					if (res != null)
+						remain.addAll(res.getConnected());
+
+				}
+			}
+
+		}
+
+		updateCursor();
+		mergee.getParent().setChanged(true);
+		ItemUtils.EnclosedCheck(mergee.getParent().getItems());
+		FrameUtils.Parse(mergee.getParent());
+
+		return remain;
+	}
+
+	/**
+	 * Picks up an item on a frame.
+	 * 
+	 * @param toGrab
+	 *            item to be picked up
+	 * @param removeItem
+	 *            true if the item should be removed from the frame
+	 */
+	public static void pickup(Item toGrab) {
+		pickup(toGrab.getConnected());
+	}
+
+	public static void pickup(List<Item> toGrab) {
+		for (Item i : toGrab) {
+			// Check if it has a relative link if so make it absolute
+			i.setAbsoluteLink();
+
+			// parent may be null
+			if (i.getParent() != null)
+				i.getParent().removeItem(i);
+
+			Frame.FreeItems.add(i);
+			i.setParent(null);
+			if (i instanceof Dot)
+				((Dot) i).setFloating(true);
+		}
+
+		updateCursor();
+
+		// if there are multiple items in the list, determine which to use for
+		// offset calculations
+		if (toGrab.size() > 1) {
+			for (Item i : toGrab) {
+				if (!(i instanceof Line)) {
+					_offX = DisplayIO.getMouseX() - i.getX() + i.getOffset().x;
+					_offY = MouseY - i.getY() + i.getOffset().y;
+
+					// make the offset item the first item in the list (so
+					// moving code knows which item to use)
+					Frame.FreeItems.set(Frame.FreeItems.indexOf(i),
+							Frame.FreeItems.get(0));
+					Frame.FreeItems.set(0, i);
+					break;
+				}
+			}
+
+			move(Frame.FreeItems);
+			// otherwise, just use the first item
+		} else if (toGrab.size() == 1) {
+			_offX = DisplayIO.getMouseX() - toGrab.get(0).getX()
+					+ toGrab.get(0).getOffset().x;
+			_offY = MouseY - toGrab.get(0).getY() + toGrab.get(0).getOffset().y;
+		}
+
+		ItemUtils.EnclosedCheck(toGrab);
+	}
+
+	private static void createLine() {
+		// create the two endpoints
+		Dot end = new Dot(DisplayIO.getRealMouseX(), DisplayIO.getMouseY(),
+				DisplayIO.getCurrentFrame().getNextItemID());
+		Dot start = new Dot(DisplayIO.getRealMouseX(), DisplayIO.getMouseY(),
+				DisplayIO.getCurrentFrame().getNextItemID());
+		// create the Line
+		Line line = new Line(start, end, DisplayIO.getCurrentFrame()
+				.getNextItemID());
+		line.autoArrowheadLength();
+
+		// anchor the start
+		anchor(start);
+
+		// attach the line to the cursor
+		pickup(end);
+	}
+
+	private static List<Item> copy(Item toCopy) {
+		return copy(toCopy.getConnected());
+	}
+
+	/**
+	 * Returns a list of copies of the list passed in
+	 * 
+	 * @param toCopy
+	 *            The list of items to copy
+	 * @return A List of copied Items
+	 */
+	private static List<Item> copy(List<Item> toCopy) {
+		return ItemUtils.CopyItems(toCopy);
+	}
+
+	public static void anchor(Item toAnchor) {
+		Frame current = DisplayIO.getCurrentFrame();
+		// toAnchor.anchor(current);
+
+		// update the items ID to prevent conflicts with the current frame
+		if (toAnchor.getID() < 0 || current.getItems().contains(toAnchor))
+			;
+		toAnchor.setID(current.getNextItemID());
+
+		toAnchor.setParent(current);
+		toAnchor.setOffset(0, 0);
+		current.addItem(toAnchor);
+		current.setResort(true);
+
+		toAnchor.setRelativeLink();
+
+		if (toAnchor instanceof Dot) {
+			for (Line line : ((Dot) toAnchor).getLines()) {
+				if (line.getID() < 0 && !current.getItems().contains(line)) {
+					line.setID(current.getNextItemID());
+					line.showHighlight(true);
+					// Mike: Why was this line here?
+					// anchor(line);
+				}
+			}
+			((Dot) toAnchor).setFloating(false);
+		} else if (toAnchor instanceof Text) {
+			// ensure all text items have their selection cleared
+			((Text) toAnchor).clearSelection();
+			((Text) toAnchor).setAlpha(0);
+		} else if (toAnchor instanceof Line) {
+			((Line) toAnchor).fixArrowheadLength();
+		}
+
+		FrameGraphics.Repaint();
+		ItemUtils.EnclosedCheck(current.getItems());
+	}
+
+	public static void anchor(List<Item> toAnchor) {
+		for (Item i : toAnchor)
+			anchor(i);
+		toAnchor.clear();
+	}
+
+	/*
+	 * private boolean mouseMovedRecently() { Date now = new Date();
+	 * 
+	 * return now.getTime() - _lastMouseMovement.getTime() < 150; }
+	 */
+
+	public void mouseWheelMoved(MouseWheelEvent arg0) {
+		// System.out.println(arg0.getClickCount() + " " +
+		// arg0.getWheelRotation());
+		int clicks = arg0.getClickCount();
+
+		// if a line is being rubber-banded, check for auto straightening
+		if (Frame.FreeItems.size() == 2) {
+			if ((Frame.FreeItems.get(0) instanceof Dot && Frame.FreeItems
+					.get(1) instanceof Line)
+					|| (Frame.FreeItems.get(1) instanceof Dot && Frame.FreeItems
+							.get(0) instanceof Line)) {
+
+				Line line;
+				if (Frame.FreeItems.get(0) instanceof Line)
+					line = (Line) Frame.FreeItems.get(0);
+				else
+					line = (Line) Frame.FreeItems.get(1);
+
+				// User must do multiple clicks to toggle the line
+				if (clicks == MOUSE_WHEEL_THRESHOLD) {
+					if (arg0.isShiftDown())
+						line.toggleArrowHeadRatio(arg0.getWheelRotation());
+					else
+						line.toggleArrowHeadLength(arg0.getWheelRotation());
+					FrameGraphics.Repaint();
+				}
+			}
+		} else if (arg0.getWheelRotation() != 0 && arg0.isShiftDown()) {
+
+			int rotationType = FrameKeyboardActions.SIZE_UP;
+			if (arg0.getWheelRotation() > 0) {
+				rotationType = FrameKeyboardActions.SIZE_DOWN;
+			}
+
+			for (int i = 0; i < clicks; i++)
+				FrameKeyboardActions.functionKey(rotationType);
+		} else if (clicks == MOUSE_WHEEL_THRESHOLD) {
+			ItemPermission ip = FrameUtils.getCurrentItem();
+
+			// if the user is not pointing to any item
+			if (ip == null) {
+				FrameGraphics
+						.DisplayMessage("There are no Items selected on the Frame");
+				return;
+			} else {
+				// check permissions
+				if (ip.Permission < ItemPermission.PERMISSION_FULL) {
+					FrameGraphics
+							.DisplayMessage("Insufficient permission to change the size of that item");
+					return;
+				}
+			}
+
+			if (ip.Item instanceof Line) {
+				Line line = (Line) ip.Item;
+				line.toggleDashed(arg0.getWheelRotation());
+				FrameGraphics.Repaint();
+				return;
+			}
+		}
+	}
+
+	/**
+	 * 
+	 * @return the integer value for the last mouse button clicked.
+	 */
+	public static int getLastMouseButton() {
+		if (_lastMouseClick == null)
+			return MouseEvent.NOBUTTON;
+
+		return _lastMouseClick.getButton();
+	}
+
+	public static boolean isDelete(MouseEvent e) {
+		if (e == null)
+			return false;
+		int onMask = MouseEvent.BUTTON3_DOWN_MASK
+				| MouseEvent.BUTTON2_DOWN_MASK;
+		return (e.getModifiersEx() & onMask) == onMask;
+	}
+
+	public static boolean isGetAttributes(MouseEvent e) {
+		if (e == null)
+			return false;
+		int onMask = MouseEvent.BUTTON3_DOWN_MASK
+				| MouseEvent.BUTTON1_DOWN_MASK;
+		return (e.getModifiersEx() & onMask) == onMask;
+	}
+
+	public static boolean isTwoClickNoOp(MouseEvent e) {
+		if (e == null)
+			return false;
+		int onMask = MouseEvent.BUTTON2_DOWN_MASK
+				| MouseEvent.BUTTON1_DOWN_MASK;
+		return (e.getModifiersEx() & onMask) == onMask;
+	}
+
+	public static boolean wasDeleteClicked() {
+		return isDelete(_lastMouseClick);
+	}
+}
Index: /trunk/src/org/expeditee/gui/FrameUtils.java
===================================================================
--- /trunk/src/org/expeditee/gui/FrameUtils.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/FrameUtils.java	(revision 6)
@@ -0,0 +1,1206 @@
+package org.expeditee.gui;
+
+import java.awt.Color;
+import java.awt.Polygon;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.expeditee.io.Logger;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemPermission;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Line;
+import org.expeditee.items.Picture;
+import org.expeditee.items.Text;
+
+public class FrameUtils {
+
+	private static final int COLUMN_WIDTH = 50;
+
+	/**
+	 * Provides a way to monitor the time elapsed between button-down and the
+	 * finished painting.
+	 */
+	public static TimeKeeper ResponseTimer = new TimeKeeper();
+
+	private static float _ResponseTimeSum = 0;
+
+	private static float _LastResponse = 0;
+
+	public static Item LastEdited = null;
+
+	public static int MINIMUM_INTERITEM_SPACING = -6;
+
+	public static float getResponseTimeTotal() {
+		return _ResponseTimeSum;
+	}
+
+	public static float getLastResponseTime() {
+		return _LastResponse;
+	}
+
+	/**
+	 * Checks if the given top Item is above the given bottom Item, allowing for
+	 * the X coordinates to be off by a certain width...
+	 * 
+	 * @param item1
+	 *            The Item to check is above the other Item
+	 * @param item2
+	 *            The Item to check is below the top Item
+	 * @return True if top is above bottom, False otherwise.
+	 */
+	public static boolean inSameColumn(Item item1, Item item2) {
+		if (!(item1 instanceof Text) || !(item2 instanceof Text))
+			return false;
+
+		if (item1.getID() < 0 || item2.getID() < 0)
+			return false;
+
+		int minX = item2.getX();
+		int maxX = item2.getX() + item2.getBoundsWidth();
+
+		int startX = item1.getX();
+		int endX = item1.getX() + item1.getBoundsWidth();
+
+		// Check that the two items left values are close
+		if (Math.abs(item1.getX() - item2.getX()) > COLUMN_WIDTH)
+			return false;
+
+		// Ensure the two items
+		if ((minX >= startX && minX <= endX)
+				|| (maxX >= startX && maxX <= endX)
+				|| (startX >= minX && startX <= maxX)
+				|| (endX >= minX && endX <= maxX))
+			return true;
+
+		return false;
+	}
+
+	public static boolean sameBulletType(String bullet1, String bullet2) {
+		if (bullet1 == null || bullet2 == null)
+			return false;
+
+		if (bullet1 == "" || bullet2 == "")
+			return false;
+
+		if (Character.isLetter(bullet1.charAt(0))
+				&& Character.isLetter(bullet2.charAt(0)))
+			return true;
+
+		if (Character.isDigit(bullet1.charAt(0))
+				&& Character.isDigit(bullet2.charAt(0)))
+			return true;
+
+		// TODO make this more sofisticated
+
+		return false;
+	}
+
+	private static boolean needsRenumbering(String s) {
+		if (s == null || s == "")
+			return false;
+		if (!Character.isLetterOrDigit(s.charAt(0)))
+			return false;
+
+		s = s.trim();
+		// if its all letters then we dont want to auto adjust
+		if (s.length() > 2) {
+			for (int i = 0; i < s.length() - 1; i++) {
+				if (!Character.isLetter(s.charAt(i)))
+					return true;
+			}
+		} else
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * 
+	 * @param toAlign
+	 * @param moveAll
+	 * @param adjust
+	 * @return
+	 */
+	public static int Align(List<Item> toAlign, boolean moveAll, int adjust) {
+		Collections.sort(toAlign);
+
+		// Single items dont need alignment
+		if (toAlign.size() < 2)
+			return 0;
+
+		// get the first item
+		Item from = toAlign.get(0);
+		if (from.getParent() == null)
+			from = toAlign.get(1);
+		int x = from.getX();
+
+		Frame curr = from.getParent();
+		Item above = curr.getItemAbove(from);
+
+		String lastBullet = "";
+
+		if (above != null && curr.isNormalTextItem(above))
+			lastBullet = FrameKeyboardActions.getAutoBullet(((Text) above)
+					.getTextNoList());
+		else {
+			lastBullet = FrameKeyboardActions.getBullet(((Text) toAlign.get(0))
+					.getTextNoList());
+		}
+		if (needsRenumbering(lastBullet)) {
+			// renumber...
+			for (int i = 0; i < toAlign.size(); i++) {
+				assert toAlign.get(i) instanceof Text;
+
+				Text currentText = ((Text) toAlign.get(i));
+				String currentBullet = FrameKeyboardActions
+						.getAutoBullet(currentText.getTextNoList());
+
+				if (sameBulletType(lastBullet, currentBullet)) {
+					currentText.stripFirstWord();
+
+					currentText.setText(lastBullet
+							+ currentText.getTextNoList());
+					lastBullet = FrameKeyboardActions.getAutoBullet(currentText
+							.getTextNoList());
+				}
+			}
+		}
+
+		// work out the spacing between the first item and the one above it
+
+		int space = 10 + adjust;
+
+		// if we are dropping from the title make the space a little bigger
+		// than normal
+		if (above != curr.getTitle() && above != null) {
+			// Make the gap between all items the same as the gap between
+			// the first two
+			space = (int) (from.getPolygon().getBounds().getMinY() - above
+					.getPolygon().getBounds().getMaxY());
+
+			if (space < MINIMUM_INTERITEM_SPACING)
+				space = MINIMUM_INTERITEM_SPACING;
+
+			if (above != curr.getName() && above != curr.getTitle())
+				x = above.getX();
+
+			space += adjust;
+
+			from.setY((int) above.getPolygon().getBounds().getMaxY()
+					+ space
+					+ ((int) (from.getY() - from.getPolygon().getBounds()
+							.getMinY())));
+
+			if (moveAll)
+				from.setX(x);
+		}
+
+		for (int i = 1; i < toAlign.size(); i++) {
+			Item current = toAlign.get(i);
+			Item top = toAlign.get(i - 1);
+
+			// The bottom of the previous item
+			int bottom = (int) top.getPolygon().getBounds().getMaxY();
+
+			// the difference between the current item's Y coordinate and
+			// the top of the highlight box
+			int diff = (int) (current.getY() - current.getPolygon().getBounds()
+					.getMinY());
+
+			int newPos = bottom + space + diff;
+
+			if (moveAll) {
+				current.setPosition(x, newPos);
+			} else if (newPos > current.getY()) {
+				current.setY(newPos);
+			}
+
+		}
+
+		// if (insert != null)
+		// return insert.getY();
+
+		// Michael thinks we return the y value for the next new item??
+		int y = from.getY() + from.getBoundsHeight() + space;
+		return y;
+	}
+
+	public static boolean LeavingFrame(Frame current) {
+		// active overlay frames may also require saving if they have been
+		// changed
+		for (Overlay o : current.getOverlays())
+			if (!SaveCheck(o.Frame))
+				return false;
+
+		// if the check fails there is no point continuing
+		if (!SaveCheck(current))
+			return false;
+
+		for (Item i : current.getItems())
+			i.showHighlight(false);
+		return true;
+	}
+
+	private static boolean SaveCheck(Frame toSave) {
+		// don't bother saving frames that haven't changed
+		if (!toSave.hasChanged())
+			return true;
+
+		// if the frame has been changed, then save it
+		if (DisplayIO.isTwinFramesOn()) {
+			Frame opposite = DisplayIO.getOppositeFrame();
+
+			String side = "left";
+			if (DisplayIO.getCurrentSide() == 0)
+				side = "right";
+
+			// if the two frames both have changes, prompt the user for the
+			// next
+			// move
+			if (opposite.hasChanged() && opposite.equals(toSave)) {
+				if (DisplayIO.DisplayConfirmDialog(
+						"Leaving this frame will discard changes made in the "
+								+ side + " Frame. Continue?", "Changes",
+						DisplayIO.TYPE_WARNING, DisplayIO.OPTIONS_OK_CANCEL,
+						DisplayIO.RESULT_OK)) {
+					FrameIO.SaveFrame(toSave);
+					DisplayIO.Reload(DisplayIO.FrameOnSide(opposite));
+					return true;
+				} else
+					return false;
+			} else if (opposite.getOverlays().contains(new Overlay(toSave, 1))) {
+				if (opposite.getOverlays().get(
+						opposite.getOverlays().indexOf(new Overlay(toSave, 1))).Frame
+						.hasChanged())
+					if (DisplayIO.DisplayConfirmDialog(
+							"Leaving this frame will discard changes made in the "
+									+ side + " Frame. Continue?", "Changes",
+							DisplayIO.TYPE_WARNING,
+							DisplayIO.OPTIONS_OK_CANCEL, DisplayIO.RESULT_OK)) {
+						FrameIO.SaveFrame(toSave);
+						DisplayIO.Reload(DisplayIO.FrameOnSide(opposite));
+						return true;
+					} else
+						return false;
+			}
+
+			// save the current frame and restore the other side
+			FrameIO.SaveFrame(toSave);
+			return true;
+		}
+
+		// single-frame mode can just save and return
+		FrameIO.SaveFrame(toSave);
+		return true;
+	}
+
+	/**
+	 * Displays the given Frame on the display. If the current frame has changed
+	 * since the last save then it will be saved before the switch is made. The
+	 * caller can also dictate whether the current frame is added to the
+	 * back-stack or not.
+	 * 
+	 * @param toDisplay
+	 *            The Frame to display on the screen
+	 * @param addToBack
+	 *            True if the current Frame should be added to the back-stack,
+	 *            False otherwise
+	 */
+	public static void DisplayFrame(Frame toDisplay, boolean addToBack) {
+		if (toDisplay == null)
+			return;
+
+		Frame current = DisplayIO.getCurrentFrame();
+
+		// move any anchored connected items
+		if (Frame.itemAttachedToCursor()) {
+			ArrayList<Item> toAdd = new ArrayList<Item>();
+
+			for (Item i : Frame.FreeItems) {
+				for (Item x : i.getConnected())
+					if (!Frame.FreeItems.contains(x) && !toAdd.contains(x))
+						toAdd.add(x);
+			}
+
+			current.removeAllItems(toAdd);
+
+			boolean oldChange = toDisplay.hasChanged();
+			toDisplay.updateIDs(toAdd);
+			toDisplay.addAllItems(toAdd);
+			toDisplay.setChanged(oldChange);
+		}
+
+		// if the saving happened properly, we can continue
+		if (!LeavingFrame(current)) {
+			FrameGraphics.DisplayMessage("Navigation cancelled");
+			return;
+		}
+
+		if (addToBack && current != toDisplay)
+			DisplayIO.addToBack(current);
+
+		Parse(toDisplay);
+		DisplayIO.setCurrentFrame(toDisplay);
+
+		// update response timer
+		_LastResponse = ResponseTimer.getElapsedSeconds();
+		_ResponseTimeSum += _LastResponse;
+	}
+
+	/**
+	 * Loads and displays the Frame with the given framename, and adds the
+	 * current frame to the back-stack if required.
+	 * 
+	 * @param framename
+	 *            The name of the Frame to load and display
+	 * @param addToBack
+	 *            True if the current Frame should be added to the back-stack,
+	 *            false otherwise
+	 */
+	public static void DisplayFrame(String frameName, boolean addToBack) {
+		Frame newFrame = getFrame(frameName);
+
+		if (newFrame != null)
+			// display the frame
+			DisplayFrame(newFrame, addToBack);
+	}
+
+	/**
+	 * Loads and displays the Frame with the given framename and adds the
+	 * current frame to the back-stack. This is the same as calling
+	 * DisplayFrame(framename, true)
+	 * 
+	 * @param framename
+	 *            The name of the Frame to load and display
+	 */
+	public static void DisplayFrame(String framename) {
+		DisplayFrame(framename, true);
+	}
+
+	public static void DisplayHomeFrame() {
+		DisplayFrame(UserSettings.FirstFrame);
+	}
+
+	public static void DisplayProfileFrame() {
+		DisplayFrame(UserSettings.Username
+				+ FrameIO.getFramesetExtension(UserSettings.Username));
+	}
+
+	public static Frame getFrame(String frameName) {
+		// if the new frame does not exist then tell the user
+		Frame f = FrameIO.LoadFrame(frameName);
+
+		if (f == null) {
+			FrameGraphics.ErrorMessage("Frame '" + frameName
+					+ "' could not be found.");
+		}
+
+		return f;
+	}
+
+	/**
+	 * Creates a new Picture Item from the given Text source Item and adds it to
+	 * the given Frame.
+	 * 
+	 * @return True if the image was created successfully, false otherwise
+	 */
+	private static boolean createPicture(Frame frame, Text txt) {
+		// attempt to create the picture
+		Picture pic = ItemUtils.CreatePicture(txt, frame);
+
+		// if the picture could not be created successfully
+		if (pic == null) {
+			assert (txt.getFirstLine() != null);
+			assert (txt.getFirstLine().split(" ").length > 1);
+
+			FrameGraphics
+					.ErrorMessage("Image " + txt.getFirstLine().split(" ")[1]
+							+ " could not be loaded");
+			return false;
+		}
+
+		// replace the Text source with the new Picture
+		frame.removeItem(txt);
+		frame.addItem(pic);
+
+		return true;
+	}
+
+	private static boolean createFramePicture(Frame frame, Text txt) {
+		if (txt.getLink() == null)
+			return false;
+
+		// attempt to create the picture
+		Picture pic = ItemUtils.CreateFramePicture(txt, frame);
+
+		// if the picture could not be created successfully
+		if (pic == null) {
+			FrameGraphics.ErrorMessage("Error loading frame image");
+			return false;
+		}
+
+		// replace the Text source with the new Picture
+		frame.removeItem(txt);
+		frame.addItem(pic);
+
+		return true;
+	}
+
+	public static void ParseProfile(Frame profile) {
+		if (profile == null)
+			return;
+
+		UserSettings.TitleTemplate = profile.getTitle();
+
+		List<Item> items = profile.getItems();
+
+		// check for all tags setting user values
+		for (Item item : items) {
+			if (ItemUtils.isTag(item, "@HomeFrame:")) {
+				String first = getLink(item, UserSettings.FirstFrame);
+				// do not use non-existant frames as the first frame
+				if (FrameIO.isValidFrameName(first)) {
+					UserSettings.FirstFrame = first;
+				}
+				// warn the user
+				else {
+					FrameGraphics.WarningMessage("Home frame: " + first
+							+ " could not be found.");
+					UserSettings.FirstFrame = profile.getFrameName();
+				}
+			} else if (ItemUtils.isTag(item, "@MenuFrame:"))
+				UserSettings.MenuFrame = getLink(item, UserSettings.MenuFrame);
+			else if (ItemUtils.isTag(item, "@DefaultFrame:"))
+				UserSettings.DefaultFrame = getLink(item,
+						UserSettings.DefaultFrame);
+			else if (ItemUtils.isTag(item, "@AntiAlias:"))
+				UserSettings.AntiAlias = getBoolean(item,
+						UserSettings.AntiAlias);
+			else if (ItemUtils.isTag(item, "@Gravity:"))
+				UserSettings.Gravity = getInt(item, UserSettings.Gravity);
+			else if (ItemUtils.isTag(item, "@ShowLineHighlight:"))
+				UserSettings.LineHighlight = getBoolean(item,
+						UserSettings.LineHighlight);
+			else if (ItemUtils.isTag(item, "@LineStraightenThreshold:"))
+				UserSettings.LineStraightenThreshold = getInt(item,
+						UserSettings.LineStraightenThreshold);
+			else if (ItemUtils.isTag(item, "@NoOpThreshold:"))
+				UserSettings.NoOpThreshold = getInt(item,
+						UserSettings.NoOpThreshold);
+			else if (ItemUtils.isTag(item, "@InitialWidth:")){
+				UserSettings.InitialWidth= getInt(item,
+						UserSettings.InitialWidth);}
+			else if (ItemUtils.isTag(item, "@InitialHeight:")){
+				UserSettings.InitialHeight= getInt(item,
+						UserSettings.InitialHeight);}
+			else if (ItemUtils.isTag(item, "@Logging:")) {
+				UserSettings.Logging = getBoolean(item, UserSettings.Logging);
+			} else if (ItemUtils.isTag(item, ItemUtils.TAG_ITEM_TEMPLATE)) {
+				UserSettings.ItemTemplate = ((Text) item).getTemplateForm();
+			} else if (ItemUtils.isTag(item, ItemUtils.TAG_ANNOTATION_TEMPLATE)) {
+				UserSettings.AnnotationTemplate = ((Text) item)
+						.getTemplateForm();
+			} else if (ItemUtils.isTag(item,
+					ItemUtils.TAG_CODE_COMMENT_TEMPLATE)) {
+				UserSettings.CodeCommentTemplate = ((Text) item)
+						.getTemplateForm();
+			} else if (ItemUtils.isTag(item, "@FrameDir:")) {
+				String dir = getDir(item, null);
+				if (dir != null)
+					UserSettings.FrameDirs.add(0, dir);
+			} else if (ItemUtils.isTag(item, "@LogDir:")) {
+				org.expeditee.gui.FrameIO.LOGS_DIR = getDir(item,
+						org.expeditee.gui.FrameIO.LOGS_DIR);
+			} else if (ItemUtils.isTag(item, "@ImageDir:")) {
+				String dir = getDir(item, null);
+				if (dir != null)
+					UserSettings.ImageDirs.add(0, dir);
+			} else if (ItemUtils.isTag(item, "@Threading:")) {
+				UserSettings.Threading = getBoolean(item,
+						UserSettings.Threading);
+			}
+		}
+
+		if (UserSettings.FirstFrame == null)
+			UserSettings.FirstFrame = profile.getFrameName();
+
+	}
+
+	private static String getLink(Item item, String alt) {
+		if (item == null || !(item instanceof Text))
+			return alt;
+
+		String value = AttributeUtils.stripValue(((Text) item).getFirstLine())
+				.trim().toLowerCase();
+		if (value.length() > 0) {
+			item.setLink(value);
+			return value;
+		} else if (item.getLink() != null)
+			return item.getLink();
+
+		return alt;
+	}
+
+	private static boolean getBoolean(Item item, boolean alt) {
+		if (item == null || !(item instanceof Text))
+			return alt;
+
+		String value = AttributeUtils.stripValue(((Text) item).getFirstLine())
+				.trim().toLowerCase();
+		if (value != null && value.length() > 0) {
+			if (value.equals("t") || value.equals("true")
+					|| value.equals("yes") || value.equals("y"))
+				return true;
+
+			if (value.equals("f") || value.equals("false")
+					|| value.equals("no") || value.equals("n")) {
+				return false;
+			}
+		}
+
+		return alt;
+	}
+
+	private static int getInt(Item item, int alt) {
+		if (item == null || !(item instanceof Text))
+			return alt;
+
+		String value = AttributeUtils.stripValue(((Text) item).getFirstLine())
+				.trim().toLowerCase();
+		try {
+			return Integer.parseInt(value);
+		} catch (Exception e) {
+		}
+
+		return alt;
+	}
+
+	private static String getDir(Item item, String alt) {
+		String name = getString(item, null);
+		if (name != null) {
+			File tester = new File(name);
+			if (tester.exists() && tester.isDirectory()) {
+				if (name.endsWith(File.separator))
+					return name;
+				else
+					return name + File.separator;
+			}
+		}
+
+		return alt;
+	}
+
+	/*
+	 * private static String FindFile(List<Item> items, String tag, String
+	 * alt){ String name = FindString(items, tag, null); if(name != null){ File
+	 * tester = new File(name); if(tester.exists()) return name; }
+	 * 
+	 * return alt; }
+	 */
+	private static String getString(Item item, String alt) {
+		if (item == null || !(item instanceof Text))
+			return alt;
+
+		String value = AttributeUtils.stripValue(((Text) item).getFirstLine())
+				.trim().toLowerCase();
+		if (value != null && value.length() > 0)
+			return value;
+
+		return alt;
+	}
+
+	public static void Parse(Frame toParse) {
+		Parse(toParse, false);
+	}
+
+	/**
+	 * Checks for any special Annotation items and updates the display as
+	 * necessary. Special Items: Images, overlays, sort.
+	 * 
+	 */
+	public static void Parse(Frame toParse, boolean firstParse) {
+		// System.out.println(firstParse);
+		if (firstParse)
+			ItemUtils.EnclosedCheck(toParse.getItems());
+
+		ArrayList<Item> items = new ArrayList<Item>();
+		items.addAll(toParse.getItems());
+
+		// if XRayMode is on, replace pictures with their underlying text
+		if (FrameGraphics.isXRayMode()) {
+			for (Item i : items) {
+				if (i instanceof Picture) {
+					toParse.removeItem(i);
+					toParse.addItem(((Picture) i).getText());
+				}
+			}
+
+			// update the list of items
+			items.clear();
+			items.addAll(toParse.getItems());
+		}
+
+		// Text title = null;
+		// Text template = UserSettings.ItemTemplate.copy();
+
+		ArrayList<Overlay> overlays = new ArrayList<Overlay>();
+
+		// disable reading of cached overlays if in twinframes mode
+		if (DisplayIO.isTwinFramesOn())
+			FrameIO.SuspendCache();
+
+		int pointtype = Item.POINTTYPE_SQUARE;
+		boolean filledPoints = true;
+
+		// check for any new overlay items
+		for (Item i : items) {
+			try {
+				/*
+				 * if (title == null && i.isTitle()) { title = (Text) i;
+				 * 
+				 * if (title.getX() <= 0 && title.getY() <= 0)
+				 * title.resetTitlePosition(); }
+				 */
+
+				// check for pointtype settings
+				if (ItemUtils.isTag(i, ItemUtils.TAG_POINTTYPE)) {
+					Text txt = (Text) i;
+					String line = txt.getFirstLine();
+					line = ItemUtils.StripTag(line, ItemUtils
+							.GetTag(ItemUtils.TAG_POINTTYPE));
+
+					if (line != null) {
+						line = line.toLowerCase();
+						if (line.indexOf(" ") > 0) {
+							String fill = line.substring(line.indexOf(" ") + 1);
+							if (fill.startsWith("nofill"))
+								filledPoints = false;
+							else
+								filledPoints = true;
+						}
+
+						if (line.startsWith("circle"))
+							pointtype = Item.POINTTYPE_CIRCLE;
+						else
+							pointtype = Item.POINTTYPE_SQUARE;
+					}
+				}
+
+				// check for Images
+				if (!FrameGraphics.isXRayMode()) {
+					if (ItemUtils.isTag(i, ItemUtils.TAG_IMAGE))
+						createPicture(toParse, (Text) i);
+					// check for frame images
+					else if (i.getLink() != null
+							&& ItemUtils.isTag(i, ItemUtils.TAG_FRAME_IMAGE))
+						createFramePicture(toParse, (Text) i);
+				}
+
+				// check for new overlay items
+				if (ItemUtils.isTag(i, ItemUtils.TAG_OVERLAY)
+						&& i.getLink() != null) {
+					Frame overlay = FrameIO.LoadFrame(i.getLink());
+					// Parse(overlay);
+					if (overlay != null && !overlays.contains(overlay))
+						overlays.add(new Overlay(overlay,
+								ItemPermission.PERMISSION_NONE));
+				}
+
+				// check for new overlay items
+				if (ItemUtils.isTag(i, ItemUtils.TAG_ACTIVE_OVERLAY)
+						&& i.getLink() != null) {
+					String link = i.getLink();
+					if (FrameIO.isPositiveInteger(link))
+						link = toParse.getFramesetNameAdjusted()
+								+ link;
+					Frame overlay = FrameIO.LoadFrame(link);
+					// Parse(overlay);
+					if (overlay != null && !overlays.contains(overlay)) {
+						// get level if specified
+						String level = ItemUtils.StripTag(((Text) i)
+								.getFirstLine());
+						// default permission (if none is specified)
+						int perm = ItemPermission.PERMISSION_FOLLOW_LINKS;
+
+						try {
+							perm = Integer.parseInt(level);
+						} catch (Exception e) {
+							// if this is a text permission level, parse it
+							if (level.length() > 0) {
+								level = level.toLowerCase().trim();
+								if (level.equals("none"))
+									perm = ItemPermission.PERMISSION_NONE;
+								else if (level.equals("links")
+										|| level.equals("link"))
+									perm = ItemPermission.PERMISSION_FOLLOW_LINKS;
+								else if (level.equals("copy"))
+									perm = ItemPermission.PERMISSION_COPY;
+								else if (level.equals("tdfc"))
+									perm = ItemPermission.PERMISSION_TDFC;
+								else if (level.equals("full")
+										|| level.equals("all"))
+									perm = ItemPermission.PERMISSION_FULL;
+								else
+									FrameGraphics
+											.DisplayMessage("Permission level '"
+													+ level
+													+ "' not recognized, using default level (Links)");
+							}
+						}
+
+						overlays.add(new Overlay(overlay, perm));
+					}
+				}
+
+				/*
+				 * check for Item templates if (ItemUtils.isTag(i,
+				 * ItemUtils.TAG_ITEM_TEMPLATE)) { template = (Text) i; }
+				 */
+
+				// only remove items on first parse
+				if (firstParse) {
+					// remove dots of size 1
+					if (i instanceof Dot) {
+						Dot dot = (Dot) i;
+						if (dot.getSize() <= 1
+								&& (dot.getLines() == null || dot.getLines()
+										.size() == 0))
+							if (dot.getConstraints() == null
+									|| dot.getConstraints().size() == 0) {
+								toParse.removeItem(dot);
+							}
+					}
+
+					// remove Text that is all whitespace
+					if (i instanceof Text) {
+						List<String> text = ((Text) i).getText();
+						// remove empty text items
+						if (text == null || text.size() == 0)
+							toParse.removeItem(i);
+						else {
+							for (String s : text)
+								if (s.trim().length() > 0)
+									break;
+								else
+									toParse.removeItem(i);
+						}
+					}
+
+				}
+
+				if (toParse.getFrameNumber() > 0
+						&& ItemUtils.isTag(i, ItemUtils.TAG_PARENT)
+						&& i.getLink() == null)
+					i.setLink(DisplayIO.getCurrentFrame().getFrameName());
+
+			} catch (Exception e) {
+				Logger.Log(e);
+				e.printStackTrace();
+				FrameGraphics.WarningMessage("Exception occured when loading "
+						+ i.getClass().getSimpleName() + "(ID: " + i.getID()
+						+ ") " + e.getMessage());
+			}
+		}
+
+		for (Item i : items) {
+			if (i instanceof Dot) {
+				((Dot) i).setPointType(pointtype);
+				((Dot) i).useFilledPoints(filledPoints);
+			}
+		}
+
+		FrameIO.ResumeCache();
+
+		synchronized (toParse) {
+			toParse.clearOverlays();
+			toParse.addAllOverlays(overlays);
+		}
+	}
+
+	/**
+	 * Searches through the list of items on this frame to find one at the given
+	 * x,y coordinates.
+	 * 
+	 * @param x
+	 *            The x coordinate
+	 * @param y
+	 *            The y coordinate
+	 * @return The Item at the given coordinates, or NULL if none is found.
+	 */
+	private static ItemPermission onItem(Frame toCheck, int x, int y) {
+		if (toCheck == null)
+			return null;
+
+		List<ItemPermission> possibles = new ArrayList<ItemPermission>(0);
+
+		// if the mouse is in the message area
+		if (y > FrameGraphics.getMaxFrameSize().getHeight()) {
+			// check the individual message items
+			for (Text message : FrameGraphics.Messages) {
+				if (message != null && message.contains(x, y))
+					possibles.add(new ItemPermission(message,
+							ItemPermission.PERMISSION_COPY));
+			}
+
+			// check the link to the message frame
+			if (FrameGraphics.MessageLink != null) {
+				if (FrameGraphics.MessageLink.contains(x, y))
+					possibles.add(new ItemPermission(FrameGraphics.MessageLink,
+							ItemPermission.PERMISSION_COPY));
+			}
+			// otherwise, the mouse is on the frame
+		} else {
+
+			if (LastEdited != null && LastEdited.contains(x, y)
+					&& !Frame.FreeItems.contains(LastEdited)
+					&& LastEdited.getParent() == DisplayIO.getCurrentFrame()
+					&& LastEdited.getParent().getItems().contains(LastEdited))
+				return new ItemPermission(LastEdited,
+						ItemPermission.PERMISSION_FULL);
+			else
+				LastEdited = null;
+
+			ArrayList<Item> checkList = new ArrayList<Item>();
+			checkList.addAll(toCheck.getItems());
+			for (Item i : checkList) {
+				// do not check annotation items in audience mode
+				if (!(FrameGraphics.isAudienceMode() && i.isAnnotation())) {
+					if (i.contains(x, y) && !Frame.FreeItems.contains(i)) {
+						// names have copy permissions only
+						if (i == toCheck.getName())
+							possibles.add(new ItemPermission(i,
+									ItemPermission.PERMISSION_TDFC));
+						else
+							possibles.add(new ItemPermission(i,
+									ItemPermission.PERMISSION_FULL));
+					}
+				}
+			}
+
+			for (Overlay o : toCheck.getOverlays()) {
+				if (o.Level > ItemPermission.PERMISSION_NONE) {
+					ItemPermission i = onItem(o.Frame, x, y);
+
+					// the message is included above with full permissions,
+					// it
+					// should be ignored here
+					if (i != null
+							&& /* i.Item != FrameGraphics.TextMessage && */i.Item
+									.getID() > 0) {
+						if (o.Level < i.Permission)
+							i.Permission = o.Level;
+						possibles.add(i);
+					}
+				}
+			}
+		}
+
+		// if there is only one possibility, return it
+		if (possibles.size() == 1)
+			return possibles.get(0);
+
+		// if there are no possible items, return null
+		if (possibles.size() == 0)
+			return null;
+
+		// return closest x,y pair to mouse
+		ItemPermission closest = possibles.get(0);
+		int distance = (int) Math.sqrt(Math.pow(Math.abs(closest.Item.getX()
+				- x), 2)
+				+ Math.pow(Math.abs(closest.Item.getY() - y), 2));
+
+		for (ItemPermission ip : possibles) {
+			Item i = ip.Item;
+
+			int d = (int) Math.sqrt(Math.pow(Math.abs(i.getX() - x), 2)
+					+ Math.pow(Math.abs(i.getY() - y), 2));
+
+			if (d < distance) {
+				distance = d;
+
+				// dots take precedence over lines
+				/**
+				 * TODO: Remove line\dot specification
+				 */
+				if ((!(closest.Item instanceof Dot && i instanceof Line))
+						&& (!(closest.Item instanceof Text && i instanceof Line)))
+					closest = ip;
+
+			}
+
+		}
+
+		return closest;
+	}
+
+	public static ItemPermission getCurrentItem() {
+		return onItem(DisplayIO.getCurrentFrame(), DisplayIO.getMouseX(),
+				DisplayIO.getMouseY());
+	}
+
+	public static List<ItemPermission> getCurrentItems() {
+		/*
+		 * // update enclosed shapes Frame current =
+		 * DisplayIO.getCurrentFrame(); List<Item> items = current.getItems();
+		 * ArrayList<Dot> dots = new ArrayList<Dot>(0);
+		 * 
+		 * for (Item i : items) if (i instanceof Dot) dots.add((Dot) i);
+		 * 
+		 * ArrayList<Polygon> possibles = new ArrayList<Polygon>(0); ArrayList<Dot>
+		 * used = new ArrayList<Dot>(0);
+		 * 
+		 * for (Dot d : dots) if (d.isEnclosed()) { boolean skip = false; //
+		 * check for duplicate enclosures for (Dot seen : used) { if
+		 * (seen.getEnclosingDots().contains(d)) { skip = true; break; } }
+		 * 
+		 * if (!skip) { Polygon p = d.getEnclosedShape(); if (p
+		 * .contains(DisplayIO.getMouseX(), DisplayIO .getMouseY())) {
+		 * possibles.add(p); used.add(d); } } }
+		 * 
+		 * if (possibles.size() == 0) return null; // if there is only one
+		 * possibility, return it if (possibles.size() == 1) { return
+		 * getItemsEnclosedBy(current, possibles.get(0)); //otherwise, determine
+		 * which polygon is closest to the cursor }else{
+		 * Collections.sort(possibles, new Comparator<Polygon>(){ public int
+		 * compare(Polygon p1, Polygon p2) { int closest = Integer.MAX_VALUE;
+		 * int close2 = Integer.MAX_VALUE;
+		 * 
+		 * int mouseX = DisplayIO.getMouseX(); int mouseY =
+		 * DisplayIO.getMouseY();
+		 * 
+		 * for(int i = 0; i < p1.npoints; i++){ int diff =
+		 * Math.abs(p1.xpoints[i] - mouseX) + Math.abs(p1.ypoints[i] - mouseY);
+		 * int diff2 = Integer.MAX_VALUE;
+		 * 
+		 * if(i < p2.npoints) diff2 = Math.abs(p2.xpoints[i] - mouseX) +
+		 * Math.abs(p2.ypoints[i] - mouseY);
+		 * 
+		 * if(diff < Math.abs(closest)){ close2 = closest; closest = diff; }else
+		 * if(diff < Math.abs(close2)) close2 = diff;
+		 * 
+		 * if(diff2 < Math.abs(closest)){ close2 = closest; closest = -diff2;
+		 * }else if(diff2 < Math.abs(close2)) close2 = diff2; }
+		 * 
+		 * if(closest > 0 && close2 > 0) return -10;
+		 * 
+		 * if(closest < 0 && close2 < 0) return 10;
+		 * 
+		 * 
+		 * if(closest > 0) return -10;
+		 * 
+		 * return 10; }
+		 * 
+		 * }); return getItemsEnclosedBy(current, possibles.get(0)); }
+		 */
+
+		List<Dot> enclosure = getEnclosingDots();
+		if (enclosure == null || enclosure.size() == 0)
+			return null;
+
+		return getItemsEnclosedBy(DisplayIO.getCurrentFrame(), enclosure.get(0)
+				.getEnclosedShape());
+	}
+
+	public static List<Dot> getEnclosingDots() {
+		// update enclosed shapes
+		Frame current = DisplayIO.getCurrentFrame();
+		List<Item> items = current.getItems();
+
+		ArrayList<Dot> used = new ArrayList<Dot>(0);
+		ArrayList<Dot> seen = new ArrayList<Dot>(0);
+
+		for (Item i : items)
+			if (i instanceof Dot) {
+				Dot d = (Dot) i;
+				if (d.isEnclosed() && !seen.contains(d)) {
+					Polygon p = d.getEnclosedShape();
+					if (p
+							.contains(DisplayIO.getMouseX(), DisplayIO
+									.getMouseY())) {
+
+						used.add(d);
+						seen.addAll(d.getEnclosingDots());
+					}
+				}
+			}
+
+		if (used.size() == 0)
+			return null;
+
+		// if there is only one possibility, return it
+		if (used.size() == 1) {
+			return used.get(0).getEnclosingDots();
+			// otherwise, determine which polygon is closest to the cursor
+		} else {
+			Collections.sort(used, new Comparator<Dot>() {
+				public int compare(Dot d1, Dot d2) {
+					Polygon p1 = d1.getEnclosedShape();
+					Polygon p2 = d2.getEnclosedShape();
+
+					int closest = Integer.MAX_VALUE;
+					int close2 = Integer.MAX_VALUE;
+
+					int mouseX = DisplayIO.getMouseX();
+					int mouseY = DisplayIO.getMouseY();
+
+					for (int i = 0; i < p1.npoints; i++) {
+						int diff = Math.abs(p1.xpoints[i] - mouseX)
+								+ Math.abs(p1.ypoints[i] - mouseY);
+						int diff2 = Integer.MAX_VALUE;
+
+						if (i < p2.npoints)
+							diff2 = Math.abs(p2.xpoints[i] - mouseX)
+									+ Math.abs(p2.ypoints[i] - mouseY);
+
+						if (diff < Math.abs(closest)) {
+							close2 = closest;
+							closest = diff;
+						} else if (diff < Math.abs(close2))
+							close2 = diff;
+
+						if (diff2 < Math.abs(closest)) {
+							close2 = closest;
+							closest = -diff2;
+						} else if (diff2 < Math.abs(close2))
+							close2 = diff2;
+					}
+
+					if (closest > 0 && close2 > 0)
+						return -10;
+
+					if (closest < 0 && close2 < 0)
+						return 10;
+
+					if (closest > 0)
+						return -10;
+
+					return 10;
+				}
+
+			});
+
+			return used.get(0).getEnclosingDots();
+		}
+	}
+
+	public static List<ItemPermission> getItemsEnclosedBy(Frame frame,
+			Polygon poly) {
+		List<Item> contained = frame.getItemsWithin(poly);
+
+		ArrayList<ItemPermission> results = new ArrayList<ItemPermission>(
+				contained.size());
+
+		// check for correct permissions
+		for (Item item : contained) {
+			// if the item is on the frame
+			if (item.getParent() == frame || item.getParent() == null)
+				// TODO this is a bit of a hack...
+				// the
+				// item
+				// parent
+				// should
+				// really
+				// be
+				// set
+				// when
+				// the
+				// polygon
+				// is
+				// created...
+				results.add(new ItemPermission(item,
+						ItemPermission.PERMISSION_FULL));
+			// otherwise, it must be on an overlay frame
+			else {
+				for (Overlay overlay : frame.getOverlays())
+					if (overlay.Frame == item.getParent()) {
+						results.add(new ItemPermission(item, overlay.Level));
+						break;
+					}
+			}
+
+		}
+
+		return results;
+	}
+
+	/**
+	 * Fills the given Frame with default profile tags
+	 */
+	public static void CreateDefaultProfile(Frame profile) {
+		Text title = profile.getTitle();
+		title.setText("Profile Frame");
+		title.setSize(50);
+		title.setFontStyle("Bold");
+		title.setFamily("SansSerif");
+		title.setColor(Color.BLUE);
+		title.setPosition(25, 50);
+
+		int spacing = 50;
+		final int intialYPos = 75;
+		int xPos = 75;
+		int yPos = intialYPos;
+
+		yPos += spacing;
+		profile.addText(xPos, yPos, "@HomeFrame: " + profile.getFrameName(),
+				null, profile.getFrameName());
+		yPos += spacing;
+		String defaultFrameName = profile.getFramesetNameAdjusted() + "0";
+		profile.addText(xPos, yPos, "@DefaultFrame: " + defaultFrameName, null,
+				defaultFrameName);
+		yPos += spacing;
+
+		profile.addText(xPos, yPos, "@InitialWidth: " + UserSettings.InitialWidth, null);
+		yPos += spacing;
+
+		profile.addText(xPos, yPos, "@InitialHeight: " + UserSettings.InitialHeight, null);
+		yPos += spacing;
+
+		Text t = profile.addText(xPos, yPos, "@ItemTemplate", null);
+		t.setColor(Color.black);
+
+		yPos += spacing;
+		t = profile.addText(xPos, yPos, "@AnnotationTemplate", null);
+		t.setColor(Color.magenta);
+
+		yPos += spacing;
+		t = profile.addText(xPos, yPos, "@CommentTemplate", null);
+		t.setColor(Item.COLOR_WHEEL[3]);
+
+		xPos = 600;
+		yPos = intialYPos;
+
+		Text help = profile.addText(xPos, yPos, "Expeditee Help", null);
+		help.setSize(25);
+		help.setFontStyle("Bold");
+		help.setFamily("SansSerif");
+		help.setColor(Item.COLOR_WHEEL[3]);
+
+		xPos = 625;
+		// Iterate through the help folder and add links
+		File helpDirectory = new File(FrameIO.HELP_PATH);
+		if (helpDirectory != null) {
+			for (File helpFrameset : helpDirectory.listFiles()) {
+				String framesetName = helpFrameset.getName();
+				Frame indexFrame = FrameIO.LoadFrame(framesetName
+						+ FrameIO.getFramesetExtension(framesetName));
+				// Look through the folder for help index pages
+				if (indexFrame != null
+						&& ItemUtils.FindTag(indexFrame.getItems(),
+								"@HelpIndex") != null) {
+					yPos += spacing;
+					t = profile.addText(xPos, yPos, indexFrame
+							.getFramesetName(), null);
+					t.setLink(indexFrame.getFrameName());
+				}
+			}
+		}
+
+		// FrameUtils.Parse(profile);
+		FrameIO.SaveFrame(profile);
+	}
+}
Index: /trunk/src/org/expeditee/gui/Overlay.java
===================================================================
--- /trunk/src/org/expeditee/gui/Overlay.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/Overlay.java	(revision 6)
@@ -0,0 +1,25 @@
+package org.expeditee.gui;
+
+public class Overlay {
+	public Frame Frame;
+
+	public int Level;
+
+	public Overlay(Frame overlay, int level) {
+		Frame = overlay;
+		Level = level;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (o == null || o.getClass() != Overlay.class)
+			return false;
+
+		return ((Overlay) o).Frame == Frame;
+	}
+
+	@Override
+	public int hashCode() {
+		return 0;
+	}
+}
Index: /trunk/src/org/expeditee/gui/TimeKeeper.java
===================================================================
--- /trunk/src/org/expeditee/gui/TimeKeeper.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/TimeKeeper.java	(revision 6)
@@ -0,0 +1,102 @@
+package org.expeditee.gui;
+
+import java.text.DecimalFormat;
+
+/**
+ * Provides methods an easy way of measuring time. Currently uses
+ * System.nanoTime to get the most precise time measurement available, results
+ * are then converted to milliseconds.
+ * 
+ * @author jdm18
+ */
+public class TimeKeeper {
+
+	/**
+	 * The Unit of measure for the returned time values
+	 */
+	public static DecimalFormat Formatter = new DecimalFormat("0.00");
+
+	private long _start = 0;
+
+	private static String MILLIS = "ms";
+
+	private static String SECONDS = "sec";
+
+	/**
+	 * Constructs a new TimeKeeper object and initiates the starting time
+	 */
+	public TimeKeeper() {
+		restart();
+	}
+
+	/**
+	 * Returns the time Sandbox has been running, in the time unit set in UNIT.
+	 * 
+	 * @return The amount of time Sandbox has been running.
+	 */
+	public static long getCurrentTime() {
+		long nanoTime = System.nanoTime();
+
+		nanoTime /= 1000000;
+
+		return nanoTime;
+	}
+
+	/**
+	 * Resets the starting time of this TimeKeeper to the value returned by
+	 * getCurrentTime()
+	 */
+	public void restart() {
+		_start = getCurrentTime();
+	}
+
+	/**
+	 * Returns the difference between the current time (getCurrentTime()) and
+	 * this TimeKeeper's starting time.
+	 * 
+	 * @return The time that has elapsed since this TimeKeeper's start time.
+	 */
+	public long getElapsedMillis() {
+		return getCurrentTime() - _start;
+	}
+
+	/**
+	 * The same result as getElapsed() but with the unit of measure (UNIT)
+	 * appended.
+	 * 
+	 * @return The time that has elapsed since this TimeKeeper's start time,
+	 *         with the unit of measure displayed.
+	 */
+	public String getElapsedStringMillis() {
+		return getElapsedMillis() + MILLIS;
+	}
+
+	public String getElapsedStringMillis(int pad) {
+		long time = getElapsedMillis();
+		String padding = "";
+		for (int i = 0; i < pad; i++)
+			if (time == 0)
+				padding += " ";
+			else
+				time /= 10;
+
+		return padding + getElapsedMillis() + MILLIS;
+	}
+
+	// returns the string in mm:ss format, with seconds shown to 3 decimal
+	// places
+	public String getElapsedStringFull() {
+		int mins = (int) Math.floor(getElapsedSeconds() / 60f);
+		float secs = getElapsedSeconds() - (mins * 60);
+
+		return mins + ":" + Formatter.format(secs);
+	}
+
+	public String getElapsedStringSeconds() {
+		return Formatter.format(getElapsedSeconds()) + SECONDS;
+	}
+
+	public float getElapsedSeconds() {
+		return getElapsedMillis() / 1000f;
+	}
+}
Index: /trunk/src/org/expeditee/gui/UserSettings.java
===================================================================
--- /trunk/src/org/expeditee/gui/UserSettings.java	(revision 6)
+++ /trunk/src/org/expeditee/gui/UserSettings.java	(revision 6)
@@ -0,0 +1,107 @@
+package org.expeditee.gui;
+
+import java.io.File;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.items.Text;
+
+/**
+ * Central class to contain all values that can be set by the user on their
+ * profile frame. These values should be updated only by
+ * FrameUtils.ParseProfile.
+ */
+public abstract class UserSettings {
+
+	public static int Gravity = 3;
+
+	public static int LineStraightenThreshold = 15;
+
+	public static int NoOpThreshold = 60;
+
+	public static int InitialWidth = 1024;
+	
+	public static int InitialHeight = 768;
+	
+	public static String Username;
+
+	public static String DefaultFrame = null;
+
+	public static String MenuFrame = null;
+
+	public static String FirstFrame = null;
+
+	public static List<String> FrameDirs = new LinkedList<String>();
+
+	public static List<String> ImageDirs = new LinkedList<String>();
+
+	public static boolean AntiAlias = false;
+
+	public static boolean LineHighlight = false;
+
+	public static boolean Logging = false;
+
+	public static boolean LogStats = true;
+
+	public static boolean Threading = true;
+
+	public static Text ItemTemplate = new Text(-1, "@ItemTemplate");
+
+	public static Text AnnotationTemplate = null;
+
+	public static Text CodeCommentTemplate = null;
+	
+	public static Text TitleTemplate = null;
+
+	// add default values
+	public static void Init() {
+		String jarPath = getJarLocation();
+		if (jarPath != null)
+			FrameIO.changeParentFolder(jarPath);
+		else
+			FrameIO.changeParentFolder("." + File.separator + "expeditee"
+					+ File.separator);
+
+		UserSettings.FrameDirs.add(FrameIO.FRAME_PATH);
+		UserSettings.FrameDirs.add(FrameIO.PROFILE_PATH);
+		UserSettings.FrameDirs.add(FrameIO.HELP_PATH);
+		UserSettings.ImageDirs.add(FrameIO.IMAGES_PATH);
+	}
+
+	/**
+	 * Find where abouts the Jar is located that is running expeditee. The
+	 * reason for this was so that the framesets could be stored in the same
+	 * location as the jar file for now. Using the current directory fails to
+	 * achieve this as the current directory not the same as the directory the
+	 * jar is stored in if the user double clicks on the jar file in Linux or
+	 * runs the jar from the command line from another directory.
+	 * 
+	 * @return
+	 */
+	public static String getJarLocation() {
+		// Get a File object for the package
+		File directory = null;
+
+		ClassLoader cld = Thread.currentThread().getContextClassLoader();
+		URL resource = cld.getResource("org");
+		directory = new File(resource.getFile());
+
+		int splitPoint = directory.getPath().indexOf('!');
+		if (splitPoint > 0) {
+			String jarName = directory.getPath().substring("file:".length(),
+					splitPoint);
+			// Windows HACK
+			if (jarName.indexOf(":") >= 0)
+				jarName = jarName.substring(1);
+
+			if (jarName.indexOf("%20") > 0) {
+				jarName = jarName.replace("%20", " ");
+			}
+
+			int lastFileSeparator = jarName.lastIndexOf(File.separator);
+			return jarName.substring(0, lastFileSeparator + 1);
+		}
+		return null;
+	}
+}
Index: /trunk/src/org/expeditee/io/Conversion.java
===================================================================
--- /trunk/src/org/expeditee/io/Conversion.java	(revision 6)
+++ /trunk/src/org/expeditee/io/Conversion.java	(revision 6)
@@ -0,0 +1,660 @@
+package org.expeditee.io;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Point;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.items.Item;
+import org.expeditee.items.Text;
+
+/**
+ * This class provides various methods for converting values to\from Java
+ * objects and KMS file values.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Conversion {
+
+	private static final double FONT_SCALE = 0.85;
+
+	// used for rounding when converting between Sandbox and KMS font sizes.
+	private static final double FONT_ROUNDING = 0.07;
+
+	// border around edges
+	public static final int X_ADJUST = 5;
+
+	public static final int Y_ADJUST = 20;
+
+	/**
+	 * Returns the Color corresponding to the given KMS color code. For example:
+	 * <br>
+	 * green4 = 0% red, 40% green, 0% blue.
+	 * 
+	 * @param colorCode
+	 *            The KMS color code to convert
+	 * @return The Color object corresponding to the given code
+	 */
+	public static Color getColor(String colorCode, Color current) {
+		if (colorCode == null || colorCode.equals("null")
+				|| colorCode.equals("none")) {
+			return current;
+		}
+
+		Color rgb = getRGBColor(colorCode, current);
+		if (rgb != null)
+			return rgb;
+
+		// separate percentage from color (if necessary)
+		String num = "";
+		int last = colorCode.length() - 1;
+
+		char c = colorCode.charAt(last);
+
+		while (c >= '0' && c <= '9') {
+			num = c + num;
+			if (last <= 0)
+				break;
+
+			c = colorCode.charAt(--last);
+		}
+
+		float amount = 10f;
+		if (num.length() > 0)
+			amount = Float.parseFloat(num);
+
+		float color[] = { 0, 0, 0 };
+
+		if (colorCode.toLowerCase().startsWith("red"))
+			color[0] = amount / 10;
+
+		if (colorCode.toLowerCase().startsWith("green"))
+			color[1] = amount / 10;
+
+		if (colorCode.toLowerCase().startsWith("blue"))
+			color[2] = amount / 10;
+
+		return new Color(color[0], color[1], color[2]);
+	}
+
+	private static Color getRGBColor(String colorCode, Color current) {
+		int color[] = { 0, 0, 0 };
+
+		try {
+			String r = colorCode.substring(0, colorCode.indexOf(" "));
+			colorCode = colorCode.substring(r.length() + 1);
+
+			String g = colorCode.substring(0, colorCode.indexOf(" "));
+			colorCode = colorCode.substring(g.length() + 1);
+
+			String b = colorCode.substring(0);
+
+			if (current != null && (r.startsWith("+") || r.startsWith("-"))) {
+				int mult = 1;
+				if (r.startsWith("-"))
+					mult = -1;
+
+				r = r.substring(1);
+
+				color[0] = combine(r, current.getRed(), mult);
+				color[1] = combine(g, current.getGreen(), mult);
+				color[2] = combine(b, current.getBlue(), mult);
+			} else {
+				color[0] = Integer.parseInt(r);
+				color[1] = Integer.parseInt(g);
+				color[2] = Integer.parseInt(b);
+
+				for (int i = 0; i < color.length; i++)
+					color[i] = (int) Math.floor((color[i] / 100.0) * 255);
+			}
+
+			for (int i = 0; i < color.length; i++) {
+				if (color[i] > 255)
+					color[i] = 255;
+
+				if (color[i] < 0)
+					color[i] = 0;
+			}
+
+			return new Color(color[0], color[1], color[2]);
+		} catch (Exception e) {
+			return current;
+		}
+	}
+
+	private static int combine(String toParse, int current, int mult) {
+		return ((int) Math.floor((Integer.parseInt(toParse) / 100.0) * 255) * mult)
+				+ current;
+	}
+
+	/**
+	 * Converts the given Color object to the corresponding KMS color code
+	 * 
+	 * @param color
+	 *            The Color to be turned into KMS color code.
+	 * @return A String containing the KMS color code, NULL if the color is
+	 *         black.
+	 */
+	public static String getKMSColorCode(Color color) {
+		if (color == null)
+			return null;
+
+		int r = (int) ((color.getRed() / 255.0) * 100 + 0.5);
+		int g = (int) ((color.getGreen() / 255.0) * 100 + 0.5);
+		int b = (int) ((color.getBlue() / 255.0) * 100 + 0.5);
+
+		return r + " " + g + " " + b;
+	}
+
+	/**
+	 * Adjusts the given x,y coordinates. This is done to allow room for the
+	 * title bar and menu bar
+	 * 
+	 * @param x
+	 *            The x coordinate
+	 * @param y
+	 *            The y coordinate
+	 * @return The new values with a small margin on the left and space on the
+	 *         top
+	 */
+	public static java.awt.Point adjust(int x, int y) {
+		return new Point(x + X_ADJUST, y + Y_ADJUST);
+	}
+
+	/**
+	 * Adjusts the given Point to allow room for the title bar and menu bar.
+	 * 
+	 * @param p
+	 *            The point to adjust.
+	 * @return A new point with the adjusted values
+	 */
+	public static Point adjust(Point p) {
+		return adjust(p.x, p.y);
+	}
+
+	/**
+	 * Restores KMS coordinates after an adjust() has been performed
+	 * 
+	 * @param point
+	 *            The Point to restore
+	 * @return a new Point with the restored values.
+	 */
+	public static java.awt.Point deAdjust(java.awt.Point point) {
+		return new Point(point.x - X_ADJUST, point.y - Y_ADJUST);
+	}
+
+	/**
+	 * Converts the given Font to a corresponding KMS font code.
+	 * 
+	 * @param font
+	 *            The Font to convert to a code.
+	 * @return The KMS font code that corresponds to the given Font.
+	 */
+	public static String getKMSFontCode(Font font) {
+		String code = "t";
+		String fontName = font.getFamily().toLowerCase();
+		
+		for (int i = 0; i < Text.FONT_WHEEL.length; i++) {
+			if (Text.FONT_WHEEL[i].equals(fontName)){
+				code = "" + Text.FONT_CHARS[i];
+				break;
+			}
+		}
+		
+		switch (font.getStyle()) {
+		case Font.BOLD:
+			code += "b";
+			break;
+		case Font.PLAIN:
+			code += "r";
+			break;
+		case Font.ITALIC:
+			code += "i";
+			break;
+		default:
+			code += "p";
+			break;
+		}
+
+		double dsize = font.getSize() / FONT_SCALE;
+		if ((Math.ceil(dsize)) - dsize < FONT_ROUNDING)
+			dsize = Math.ceil(dsize);
+		else
+			dsize = Math.floor(dsize);
+
+		code += (int) dsize;
+
+		return code;
+	}
+
+	/**
+	 * Converts the given KMS font code to a Font object.<br>
+	 * For example: <br>
+	 * tr16 = times, roman, 16pt
+	 * 
+	 * @param fontCode
+	 *            The font code to convert to a Font
+	 * @return the Font that corresponds to the given font code.
+	 */
+	public static Font getFont(String fontCode) {
+		String code = Text.FONT_WHEEL[0];
+
+		char c = fontCode.charAt(0);
+		for (int i = 0; i < Text.FONT_CHARS.length; i++) {
+			if (c == Text.FONT_CHARS[i]) {
+				code = Text.FONT_WHEEL[i] + '-';
+				break;
+			}
+		}
+
+		switch (fontCode.charAt(1)) {
+		case 'r':
+			code += "Plain";
+			break;
+		case 'b':
+			code += "Bold";
+			break;
+		case 'i':
+			code += "Italic";
+			break;
+		case 'p':
+			code += "BoldItalic";
+			break;
+		}
+
+		code += "-";
+
+		Font font = null;
+
+		try {
+			int size = Integer.parseInt(fontCode.substring(2));
+			double dsize = size * FONT_SCALE;
+			if (dsize - ((int) dsize) > FONT_ROUNDING)
+				dsize = Math.ceil(dsize);
+
+			code += (int) dsize;
+
+			font = Font.decode(code);
+		} catch (NumberFormatException nfe) {
+			font = Font.decode(fontCode);
+		}
+
+		return font;
+	}
+
+	/**
+	 * Returns the number portion (Frame Number) of the given Frame name.
+	 * 
+	 * @param framename
+	 *            The Frame name to extract the number from
+	 * @return The Frame number
+	 */
+	public static int getFrameNumber(String framename) {
+		String num = framename
+				.replaceFirst("^\\w+([^\\d]+|\\d+[.][^\\d]+)", "");
+
+		try {
+			return Integer.parseInt(num);
+		} catch (NumberFormatException nfe) {
+			return -1;
+		}
+	}
+
+	/**
+	 * Returns the frameset poriton of the given Frame name (frame number
+	 * removed)
+	 * 
+	 * @param frame
+	 *            The full name to extract the Frameset name from
+	 * @return the name of the Frameset extracted from the given Frame name.
+	 */
+	public static String getFrameset(String frame) {
+		return getFrameset(frame, true);
+	}
+
+	public static String getFrameset(String frame, boolean convertToLower) {
+		String set = frame.replaceFirst("(\\d+|[.]\\d+)$", "");
+
+		// if there is no frameset given, then add the current frameset
+		if (set.length() == 0) {
+			set = DisplayIO.getCurrentFrame().getFramesetName();
+		}
+
+		if (convertToLower)
+			return set.toLowerCase();
+
+		return set;
+	}
+
+	/**
+	 * Converts the given KMS justification code into a int corresponding to the
+	 * constants defined in Item.
+	 * 
+	 * @param justCode
+	 *            The KMS justification code to convert
+	 * @return The resulting int corresponding to one of the constants defined
+	 *         in Item
+	 */
+	public static int getJustification(String justCode) {
+		// as read from file
+		if (justCode.length() == 1) {
+			switch (justCode.charAt(0)) {
+			case 'F':
+				return Item.JUSTIFICATION_FULL;
+			case 'L':
+				return Item.JUSTIFICATION_LEFT;
+			case 'R':
+				return Item.JUSTIFICATION_RIGHT;
+			case 'C':
+				return Item.JUSTIFICATION_CENTER;
+			}
+		}
+
+		// from the user
+		justCode = justCode.trim().toLowerCase();
+		if (justCode.equals("center"))
+			return Item.JUSTIFICATION_CENTER;
+		if (justCode.equals("left"))
+			return Item.JUSTIFICATION_LEFT;
+		if (justCode.equals("right"))
+			return Item.JUSTIFICATION_RIGHT;
+		if (justCode.equals("full"))
+			return Item.JUSTIFICATION_FULL;
+
+		// default justification
+		return Item.JUSTIFICATION_NONE;
+	}
+
+	public static String getKMSJustificationCode(int justification) {
+		switch (justification) {
+		case Item.JUSTIFICATION_CENTER:
+			return "C";
+		case Item.JUSTIFICATION_FULL:
+			return "F";
+		case Item.JUSTIFICATION_LEFT:
+			return "L";
+		case Item.JUSTIFICATION_RIGHT:
+			return "R";
+		default:
+			return null;
+		}
+
+	}
+
+	public static Object Convert(Class type, String value) {
+		return Convert(type, value, null);
+	}
+
+	// Will convert from KMS color values to RGB...
+	public static Object Convert(Class type, String value, Object orig) {
+		// System.out.println("Orig: " + orig);
+
+		if (type == String.class
+				&& String.CASE_INSENSITIVE_ORDER.compare(value, "") == 0)
+			return null;
+		else if (type == String.class)
+			return value;
+
+		/*
+		 * if(type == int.class || type == Integer.class){ return
+		 * Integer.parseInt(value); }
+		 * 
+		 * if(type == float.class || type == Float.class){ return
+		 * Float.parseFloat(value); }
+		 */
+
+		if (type == Font.class) {
+			return getFont(value);
+		}
+
+		if (type.equals(Color.class)) {
+			if (value.length() == 0)
+				return null;
+
+			try {
+				// Find the field and value of colorName
+				Field field = Class.forName("java.awt.Color").getField(
+						value.toLowerCase());
+				return (Color) field.get(null);
+			} catch (Exception e) {
+				return getColor(value, (Color) orig);
+			}
+		}
+
+		if (type.equals(int.class)) {
+			if (orig instanceof Integer
+					&& (value.startsWith("+") || value.startsWith("-"))) {
+				value = value.replace("+", "");
+
+				return ((Integer) orig) + Integer.parseInt(value);
+			}
+
+			if (value.equals("null"))
+				return -1;
+
+			return Integer.parseInt(value);
+		}
+
+		if (type.equals(float.class)) {
+			if (orig instanceof Float
+					&& (value.startsWith("+") || value.startsWith("-"))) {
+				value = value.replace("+", "");
+
+				return ((Float) orig) + Float.parseFloat(value);
+			}
+
+			return Float.parseFloat(value);
+		}
+
+		if (type.equals(double.class)) {
+			if (orig instanceof Double
+					&& (value.startsWith("+") || value.startsWith("-"))) {
+				value = value.replace("+", "");
+
+				return ((Double) orig) + Double.parseDouble(value);
+			}
+
+			return Double.parseDouble(value);
+		}
+
+		if (type.equals(int[].class)) {
+			StringTokenizer st = new StringTokenizer(value, " ");
+			int[] param = new int[st.countTokens()];
+			for (int i = 0; i < param.length; i++) {
+				try {
+					param[i] = Integer.parseInt(st.nextToken());
+				} catch (Exception e) {
+					return null;
+				}
+			}
+
+			return param;
+		}
+
+		if (type.equals(Font.class)) {
+			return Conversion.getFont(value);
+		}
+
+		if (type.equals(boolean.class)) {
+			value = value.toLowerCase().trim();
+
+			if (value.equals("t") || value.equals("true")
+					|| value.equals("yes") || value.equals("y")
+					|| value.equals(""))
+				return true;
+
+			if (value.equals("f") || value.equals("false")
+					|| value.equals("no") || value.equals("n"))
+				return false;
+
+			return null;
+		}
+
+		// return name + ": " + value;
+		/*
+		 * String[] val = new String[1]; val[0] = value;
+		 */
+		return null;
+
+	}
+
+	public static Object[] Convert(Method method, String value) {
+		return Convert(method, value, null);
+	}
+
+	public static Object[] Convert(Method method, String value, Object current) {
+
+		String name = method.getName();
+		Class[] types = method.getParameterTypes();
+		if (name.equals("setPosition")) {
+			Point[] p = new Point[1];
+			p[0] = new Point();
+			p[0].x = Integer.parseInt(value.substring(0, value.indexOf(" ")));
+			p[0].y = Integer.parseInt(value.substring(value.indexOf(" ") + 1));
+			p[0] = adjust(p[0]);
+			return p;
+		}
+
+		if (name.equals("setJustification")) {
+			Integer[] just = new Integer[1];
+			just[0] = getJustification(value);
+			return just;
+		}
+
+		if (name.endsWith("Arrow")) {
+			if (value.indexOf(" ") < 0)
+				return null;
+
+			Integer a = Integer
+					.parseInt(value.substring(0, value.indexOf(" ")));
+			Double b = Double.parseDouble(value
+					.substring(value.indexOf(" ") + 1));
+
+			Object[] vals = new Object[2];
+			vals[0] = a;
+			vals[1] = b;
+			return vals;
+		}
+
+		if (types.length == 1 && types[0] == List.class) {
+			StringTokenizer st = new StringTokenizer(value, "\n");
+			List<String> list = new LinkedList<String>();
+			while (st.hasMoreTokens())
+				list.add(st.nextToken());
+
+			Object[] vals = new Object[1];
+			vals[0] = list;
+			return vals;
+		}
+
+		if (types.length == 1) {
+			Object o[] = new Object[1];
+			o[0] = Convert(types[0], value, current);
+			return o;
+		}
+		/*
+		 * if(types.length == 1 && types[0] == int.class){ Convert(types[0],
+		 * value);
+		 * 
+		 * Integer[] val = new Integer[1]; val[0] = Integer.parseInt(value);
+		 * return val; }
+		 * 
+		 * if(types.length == 1 && types[0] == float.class){ Float[] val = new
+		 * Float[1]; val[0] = Float.parseFloat(value); return val; }
+		 * 
+		 * if(types.length == 1 && types[0] == Font.class){ Font f =
+		 * getFont(value); if(f != null){ Font[] res = new Font[1]; res[0] = f;
+		 * return res; }
+		 * 
+		 * return null; }
+		 * 
+		 * if(types.length == 1 && types[0] == Color.class){ Color c =
+		 * getColor(value, null); if(c != null){ Color[] res = new Color[1];
+		 * res[0] = c; return res; }
+		 * 
+		 * return null; }
+		 * 
+		 * if(types.length == 1 && types[0] == int[].class){ if(value.length() ==
+		 * 0) return null;
+		 * 
+		 * StringTokenizer st = new StringTokenizer(value, " "); int[] param =
+		 * new int[st.countTokens()]; for(int i = 0; i < param.length; i++){
+		 * try{ param[i] = Integer.parseInt(st.nextToken()); }catch(Exception
+		 * e){ return null; } }
+		 * 
+		 * Object[] o = new Object[1]; o[0] = param; return o; }
+		 */
+
+		// return name + ": " + value;
+		String[] val = new String[1];
+		val[0] = value;
+		return val;
+	}
+
+	public static Object ConvertToKMS(Method method, Object output) {
+		if (output == null)
+			return null;
+
+		if (method == null) {
+			System.out.println("Output: " + output);
+		}
+
+		String name = method.getName();
+
+		if (output instanceof List)
+			return output;
+
+		if (name.endsWith("Justification"))
+			return getKMSJustificationCode((Integer) output);
+
+		// strings can be returned immediately
+		if (output instanceof String)
+			return (String) output;
+
+		// integers can also be returned immediately
+		if (output instanceof Integer && (Integer) output >= 0)
+			return "" + output;
+
+		// floats can also be returned immediately
+		if (output instanceof Float && (Float) output >= 0)
+			return "" + output;
+
+		// convert fonts
+		if (output instanceof Font)
+			return getKMSFontCode((Font) output);
+
+		// convert colors
+		if (output instanceof Color)
+			return getKMSColorCode((Color) output);
+
+		// covert points
+		if (output instanceof Point)
+			return (((Point) output).x - X_ADJUST) + " "
+					+ (((Point) output).y - Y_ADJUST);
+
+		if (output instanceof Boolean)
+			if ((Boolean) output)
+				return null;
+			else
+				return "F";
+
+		if (output instanceof int[]) {
+			int[] out = (int[]) output;
+			String res = "";
+			for (int i : out)
+				res += i + " ";
+
+			res = res.trim();
+			if (res.length() > 0)
+				return res;
+		}
+		// default
+		return null;
+	}
+}
Index: /trunk/src/org/expeditee/io/DefaultFrameWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/DefaultFrameWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/DefaultFrameWriter.java	(revision 6)
@@ -0,0 +1,127 @@
+package org.expeditee.io;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.items.Item;
+
+public abstract class DefaultFrameWriter extends ItemWriter implements
+		FrameWriter {
+
+	protected String _filename = null;
+
+	protected String _output = null;
+
+	protected ProxyWriter _writer = null;
+
+	protected String _format = "";
+
+	protected boolean _running = true;
+
+	protected boolean _stop = false;
+
+	public void setOutputLocation(String filename) {
+		_filename = filename;
+	}
+
+	public String writeFrame(Frame toWrite) throws IOException {
+		try {
+			initialise(toWrite);
+
+			outputFrame(toWrite);
+
+			_running = false;
+			return finaliseFrame();
+		} catch (IOException ioe) {
+			_running = false;
+			throw ioe;
+		}
+
+	}
+
+	protected void writeStartFrame(Frame starting) throws IOException {
+		if (starting.getTitle() != null) {
+			if (starting.getTitle().isAnnotation())
+				this.writeAnnotationTitle(starting.getTitle());
+			else
+				this.writeTitle(starting.getTitle(), starting.getItems());
+		}
+	}
+
+	protected void writeEndFrame(Frame ending) throws IOException {
+	}
+
+	protected void initialise(Frame start) throws IOException {
+		if (_filename == null)
+			_filename = FrameIO.EXPORTS_DIR + start.getFrameName() + _format;
+
+		if (_filename.toLowerCase().equals("clipboard")) {
+			_writer = new ProxyWriter(true);
+			_output = "Clipboard";
+		} else {
+			if (_filename.contains(File.separator)) {
+				String extTest = _filename.substring(_filename
+						.lastIndexOf(File.separator) + 1);
+				if (!extTest.contains("."))
+					_filename += _format;
+			} else if (!_filename.contains("."))
+				_filename += _format;
+
+			if (!_filename.contains(File.separator))
+				_filename = FrameIO.EXPORTS_DIR + _filename;
+
+			File test = new File(_filename);
+
+			test = test.getParentFile();
+			if (test != null && !test.exists()) {
+				test.mkdirs();
+			}
+
+			_writer = new ProxyWriter(new FileWriter(_filename));
+			_output = _filename;
+		}
+	}
+
+	protected String finalise() throws IOException {
+		try {
+			_writer.flush();
+			_writer.close();
+		} catch (IOException ioe) {
+		} finally {
+			_writer.close();
+		}
+
+		return " exported to " + _output;
+	}
+
+	protected String finaliseFrame() throws IOException {
+		return "Frame" + finalise();
+	}
+
+	protected void outputFrame(Frame toWrite) throws IOException {
+		writeStartFrame(toWrite);
+
+		for (Item i : toWrite.getItems()) {
+			if (_stop)
+				return;
+			writeItem(i);
+		}
+
+		writeEndFrame(toWrite);
+	}
+
+	public boolean isRunning() {
+		return _running;
+	}
+
+	public void stop() {
+		_stop = true;
+	}
+
+	public String getFileContents() {
+		return "Not Supported";
+	}
+}
Index: /trunk/src/org/expeditee/io/DefaultTreeWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/DefaultTreeWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/DefaultTreeWriter.java	(revision 6)
@@ -0,0 +1,164 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Stack;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.items.Item;
+
+public abstract class DefaultTreeWriter extends DefaultFrameWriter implements
+		TreeWriter {
+
+	// the list of frames currently being processed
+	private Stack<FrameCounter> _frames = new Stack<FrameCounter>();
+
+	private int _frameCount = 0;
+
+	public String writeTree(Frame toWrite) throws IOException {
+		try {
+			initialise(toWrite);
+
+			outputTree(toWrite);
+
+			_running = false;
+			return finaliseTree();
+		} catch (IOException ioe) {
+			_running = false;
+			throw ioe;
+		}
+	}
+
+	/**
+	 * This method is used to output any tags before following the Item's link
+	 * when using tree writers.
+	 * 
+	 * @param linker
+	 *            The linked Item that is about to be followed.
+	 * @throws IOException
+	 */
+	protected void writeStartLink(Item linker) throws IOException {
+
+	}
+
+	/**
+	 * This method is called after the Frame the Item links to has been
+	 * processed. This allows end tags to be written if the format requires it.
+	 * 
+	 * @param linker
+	 *            The Item whose link was just followed.
+	 * @throws IOException
+	 */
+	protected void writeEndLink(Item linker) throws IOException {
+
+	}
+
+	protected void resumeFrame(Frame resuming) {
+	}
+
+	protected void outputTree(Frame toWrite) throws IOException {
+		if (toWrite == null)
+			return;
+
+		_frames.push(new FrameCounter(toWrite.getFrameName(), -1));
+
+		// process the entire tree of frames in depth-first order
+		while (_frames.size() > 0) {
+			FrameCounter cur = _frames.pop();
+
+			Frame next = FrameIO.LoadFrame(cur.frame);
+			if (next == null) {
+				return;
+			}
+
+			// the items on the frame currently being processed.
+			List<Item> items = next.getItems();
+
+			// write any information relating to the end of the link
+			if (cur.index > 0) {
+				this.writeEndLink(items.get(cur.index));
+				this.resumeFrame(next);
+			} else {
+				FrameGraphics.OverwriteMessage("Writing: "
+						+ next.getFrameName());
+				_frameCount++;
+				writeStartFrame(next);
+			}
+
+			boolean complete = true;
+
+			// resume from the next item in the list
+			for (int i = cur.index + 1; i < items.size(); i++) {
+				if (_stop)
+					return;
+
+				Item item = items.get(i);
+
+				// ignore annotation and framenames
+				if (item.getID() >= 0) {
+					if (item.getLink() != null && !item.isAnnotation()) {
+						cur.index = i;
+						_frames.push(cur);
+
+						// write any information relating to the start of the
+						// link
+						this.writeStartLink(item);
+
+						Frame linked = FrameIO.LoadFrame(item.getLink());
+
+						// if the linked frame was found, then display it next
+						if (linked != null) {
+							FrameCounter fc = new FrameCounter(linked
+									.getFrameName(), -1);
+							if (!_frames.contains(fc)) {
+								// remember what frame we are on before
+								// processing it
+								_frames.push(fc);
+
+								complete = false;
+								// process the loaded frame immediately
+								// (depth-first)
+								break;
+							}
+						}
+					} else if (item != next.getTitle())
+						this.writeItem(item);
+				}
+			}
+
+			if (complete)
+				writeEndFrame(next);
+		}
+	}
+
+	protected String finaliseTree() throws IOException {
+		return "Tree of " + _frameCount + " frames" + finalise();
+	}
+
+	/**
+	 * Inner class used to keep track of what frames have been seen, as well as
+	 * what Item in the Frame the processing was up to. Only Frame names are
+	 * stored to keep memory usage down.
+	 */
+	private class FrameCounter {
+		public int index;
+
+		public String frame;
+
+		public FrameCounter(String f, int i) {
+			frame = f.toLowerCase();
+			index = i;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof FrameCounter)
+				return (((FrameCounter) o).frame.equals(frame));// && fc.index
+																// == index);
+
+			return false;
+		}
+	}
+}
Index: /trunk/src/org/expeditee/io/ExpReader.java
===================================================================
--- /trunk/src/org/expeditee/io/ExpReader.java	(revision 6)
+++ /trunk/src/org/expeditee/io/ExpReader.java	(revision 6)
@@ -0,0 +1,332 @@
+package org.expeditee.io;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Point;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.items.Constraint;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Text;
+
+/**
+ * Reads in Exp format files and constructs the Frame and Item objects they
+ * contain.
+ * 
+ * @author mrww1
+ * 
+ */
+public class ExpReader implements FrameReader {
+
+	public static final String EXTENTION = ".exp";
+
+	private BufferedReader _reader = null;
+
+	private static LinkedHashMap<Character, Method> _ItemTags = null;
+
+	private static LinkedHashMap<Character, Method> _FrameTags = null;
+
+	private String _frameName;
+	
+	/**
+	 * Does nothing, location must be set before use
+	 */
+	public ExpReader(String frameName) {
+		_frameName = frameName;
+		
+		if (_ItemTags != null && _FrameTags != null)
+			return;
+		
+		_ItemTags = new LinkedHashMap<Character, Method>();
+		_FrameTags = new LinkedHashMap<Character, Method>();
+
+		Class[] pString = { String.class };
+		Class[] pInt = { int.class };
+		Class[] pFloat = { float.class };
+		Class[] pColor = { Color.class };
+		Class[] pBool = { boolean.class };
+		Class[] pFont = { Font.class };
+		Class[] pPoint = { Point.class };
+		Class[] pArrow = { int.class, double.class };
+		Class[] pList = { List.class };
+		Class[] pIntArray = { int[].class };
+		Class[] pItem = { Item.class };
+
+		try {
+			_FrameTags.put('V', Frame.class.getMethod("setVersion", pInt));
+			_FrameTags
+					.put('p', Frame.class.getMethod("setProtection", pString));
+			_FrameTags.put('U', Frame.class.getMethod("setOwner", pString));
+			_FrameTags.put('D', Frame.class
+					.getMethod("setDateCreated", pString));
+			_FrameTags.put('M', Frame.class.getMethod("setLastModifyUser",
+					pString));
+			_FrameTags.put('d', Frame.class.getMethod("setLastModifyDate",
+					pString));
+			_FrameTags
+					.put('F', Frame.class.getMethod("setFrozenDate", pString));
+
+			_FrameTags.put('O', Frame.class.getMethod("setForegroundColor",
+					pColor));
+			_FrameTags.put('B', Frame.class.getMethod("setBackgroundColor",
+					pColor));
+
+			_ItemTags.put('S', Item.class.getMethod("setID", pInt));
+			_ItemTags.put('s', Item.class.getMethod("setDateCreated", pString));
+			_ItemTags.put('d', Item.class.getMethod("setColor", pColor));
+			_ItemTags.put('G', Item.class.getMethod("setBackgroundColor",
+					pColor));
+			_ItemTags.put('P', Item.class.getMethod("setPosition", pPoint));
+			_ItemTags.put('F', Item.class.getMethod("setLink", pString));
+			_ItemTags.put('X', Item.class.getMethod("setAction", pList));
+			_ItemTags.put('x', Item.class.getMethod("setActionMark", pBool));
+			_ItemTags.put('U', Item.class.getMethod("setActionCursorEnter",
+					pList));
+			_ItemTags.put('V', Item.class.getMethod("setActionCursorLeave",
+					pList));
+			_ItemTags.put('W', Item.class.getMethod("setActionEnterFrame",
+					pList));
+			_ItemTags.put('Y', Item.class.getMethod("setActionLeaveFrame",
+					pList));
+			_ItemTags.put('D', Item.class.getMethod("setData", pString));
+			_ItemTags.put('u', Item.class.getMethod("setHighlight", pBool));
+			_ItemTags.put('e', Item.class.getMethod("setFillColor", pColor));
+			_ItemTags.put('i', Item.class.getMethod("setFillPattern", pString));
+			_ItemTags.put('o', Item.class.getMethod("setOwner", pString));
+			_ItemTags.put('n', Item.class.getMethod("setLinkMark", pBool));
+			_ItemTags
+					.put('q', Item.class.getMethod("setLinkFrameset", pString));
+			_ItemTags
+					.put('y', Item.class.getMethod("setLinkTemplate", pString));
+			_ItemTags.put('g', Item.class
+					.getMethod("setLinePattern", pIntArray));
+
+			_ItemTags.put('j', Item.class.getMethod("setArrow", pArrow));
+
+			_ItemTags.put('f', Text.class.getMethod("setFont", pFont));
+			_ItemTags.put('t', Text.class.getMethod("setSpacing", pInt));
+			_ItemTags.put('T', Text.class.getMethod("appendLine", pString));
+
+			_ItemTags.put('a', Text.class.getMethod("setWordSpacing", pInt));
+			_ItemTags.put('b', Text.class.getMethod("setLetterSpacing", pInt));
+			_ItemTags.put('m', Text.class.getMethod("setInitialSpacing", pInt));
+			_ItemTags.put('w', Text.class.getMethod("setWidth", pInt));
+			_ItemTags.put('k', Text.class.getMethod("setJustification", pInt));
+
+			_ItemTags.put('h', Dot.class.getMethod("setThickness", pFloat));
+			_ItemTags.put('l', Item.class.getMethod("setLineIDs", pString));
+			_ItemTags
+					.put('c', Dot.class.getMethod("setConstraintIDs", pString));
+
+			// Lines and constraints are created differently
+			_ItemTags.put('L', Line.class.getMethod("setStartItem", pItem));
+			_ItemTags.put('C', Constraint.class.getMethod("getID",
+					(Class[]) null));
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Determines whether a string begins with a KMS tag.
+	 * 
+	 * @param s
+	 *            a line of text
+	 * @return true if s begins with a KMS tag
+	 */
+	private boolean isValidLine(String s) {
+		return s.length() >= 2 && s.charAt(1) == ' '
+				&& Character.isLetter(s.charAt(0));
+	}
+
+	/**
+	 * Reads a KMS file with the given name from disk.
+	 * 
+	 * @param frameName
+	 *            the name of the Frame to read in from a file.
+	 * @return A new Frame object that contains any Items described in the file.
+	 * @throws IOException
+	 *             Any exceptions occured by the BufferedReader.
+	 */
+	public Frame readFrame(String fullpath) throws IOException {
+		_reader = new BufferedReader(new FileReader(fullpath));
+		String next = "";
+		Frame newFrame = new Frame();
+
+		try {
+			//Framename must be set before setting the frame number
+			newFrame.setFrameName(_frameName);
+			
+			// First read all the header lines
+			while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
+				if (isValidLine(next)) {
+					processHeaderLine(newFrame, next);
+				}
+			}
+			
+			// Now read all the items
+			Item currentItem = null;
+			while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
+				// if this is the start of a new item add a new item
+				if (isValidLine(next) ) {
+					if (getTag(next) == 'S') {
+						String value = getValue(next);
+						int id = Integer.parseInt(value.substring(2));
+						
+						switch (value.charAt(0)) {
+						case 'P': //check if its a point
+							currentItem = new Dot(id);
+							_linePoints.put(currentItem.getID(), currentItem);
+							break;
+						default:
+							currentItem = new Text(id);
+							break;
+						}
+						newFrame.addItem(currentItem);
+					} else if (currentItem != null) {
+						processBodyLine(currentItem, next);
+					}
+				}
+			}
+
+			// Read the lines
+			while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
+				if (isValidLine(next)) {
+					java.awt.Point idtype = separateValues(next.substring(2));
+					// The next line must be the endpoints
+					if (!_reader.ready())
+						throw new Exception("Unexpected end of file");
+					next = _reader.readLine();
+					java.awt.Point startend = separateValues(next.substring(2));
+					int start = startend.x;
+					int end = startend.y;
+
+					if (_linePoints.get(start) != null
+							&& _linePoints.get(end) != null) {
+						newFrame.addItem(new Line(_linePoints.get(start),
+								_linePoints.get(end), idtype.x));
+					} else {
+						System.out
+								.println("Error reading line with unknown end points");
+					}
+				}
+			}
+
+			// Read the constraints
+			while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
+				if (isValidLine(next)) {
+					java.awt.Point idtype = separateValues(next.substring(2));
+					// The next line must be the endpoints
+					if (!_reader.ready())
+						throw new Exception("Unexpected end of file");
+					next = _reader.readLine();
+					java.awt.Point startend = separateValues(next.substring(2));
+
+					Dot a = (Dot) _linePoints.get(startend.x);
+					Dot b = (Dot) _linePoints.get(startend.y);
+
+					new Constraint(a, b, idtype.x, idtype.y);
+				}
+			}
+
+		} catch (Exception e) {
+			System.out.println("Error reading frame file line: " + next + " "
+					+ e.getMessage());
+		}
+
+		_reader.close();
+		newFrame.setChanged(false);
+
+		return newFrame;
+	}
+
+	// Stores points used when constructing lines
+	private HashMap<Integer, Item> _linePoints = new HashMap<Integer, Item>();
+
+	/**
+	 * Processes the body section of the Exp file, which contains all Items
+	 * 
+	 * @param frame
+	 *            The Frame to add any created Items to.
+	 * @param line
+	 *            The line of text read in from the file to process.
+	 */
+	private void processBodyLine(Item item, String line) {
+		// separate the tag from the value
+		Character tag = getTag(line);
+		String value = getValue(line);
+
+		Method toRun = _ItemTags.get(tag);
+		if (toRun == null)
+			System.out.println("Error accessing tag method: " + tag);
+		Object[] vals = Conversion.Convert(toRun, value);
+
+		try {
+			if (vals != null)
+				toRun.invoke(item, vals);
+		} catch (Exception e) {
+			System.out.println("Error running tag method: " + tag);
+			//e.printStackTrace();
+		}
+	}
+
+	private Character getTag(String line) {
+		assert (line.length() > 0);
+		return line.charAt(0);
+	}
+
+	private String getValue(String line) {
+		if (line.length() > 2)
+			return line.substring(2);
+		else
+			return "";
+	}
+
+	/**
+	 * Reads the header section of the file, which contains information about
+	 * the Frame.
+	 * 
+	 * @param frame
+	 *            The Frame to assign the read values to.
+	 * @param line
+	 *            The line from the file to process.
+	 * @return False if the end of the header has been reached, True otherwise.
+	 */
+	private void processHeaderLine(Frame frame, String line) throws IOException {
+		// first separate the tag from the text
+		Character tag = getTag(line);
+		String value = getValue(line);
+		Method toRun = _FrameTags.get(tag);
+
+		if (toRun == null) {
+			System.out.println("Tag '" + tag + "' in '" + line
+					+ "' is not supported.");
+			return;
+		}
+
+		Object[] vals = Conversion.Convert(toRun, value);
+		try {
+			toRun.invoke(frame, vals);
+		} catch (Exception e) {
+			System.out.println("Error running method: " + toRun.toGenericString());
+			e.printStackTrace();
+		}
+	}
+
+	// Returns a point from a String containing two ints separated by a space
+	private java.awt.Point separateValues(String line) {
+		int x = Integer.parseInt(line.substring(0, line.indexOf(" ")));
+		int y = Integer.parseInt(line.substring(line.indexOf(" ") + 1));
+
+		return new java.awt.Point(x, y);
+	}
+}
Index: /trunk/src/org/expeditee/io/ExpWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/ExpWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/ExpWriter.java	(revision 6)
@@ -0,0 +1,342 @@
+package org.expeditee.io;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.items.Constraint;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Picture;
+import org.expeditee.items.Text;
+import org.expeditee.stats.SessionStats;
+
+/**
+ * Writes a Frame out to a KMS format file.
+ * 
+ * @author jdm18
+ * 
+ */
+public class ExpWriter extends DefaultFrameWriter {
+
+	private ProxyWriter _writer = null;
+
+	private StringBuilder _stringWriter = null;
+
+	// keep track of methods that are put on the same line
+	private static LinkedHashMap<String, Method> _ItemTags = null;
+
+	private static LinkedHashMap<String, Method> _FrameTags = null;
+
+	private static final char TERMINATOR = 'Z';
+
+	public ExpWriter() {
+		if (_ItemTags != null && _FrameTags != null)
+			return;
+
+		_ItemTags = new LinkedHashMap<String, Method>();
+		_FrameTags = new LinkedHashMap<String, Method>();
+
+		Class[] param = {};
+
+		try {
+			_FrameTags.put("V", Frame.class.getMethod("getVersion", param));
+			_FrameTags.put("p", Frame.class.getMethod("getProtection", param));
+			_FrameTags.put("U", Frame.class.getMethod("getOwner", param));
+			_FrameTags.put("D", Frame.class.getMethod("getDateCreated", param));
+			_FrameTags.put("M", Frame.class.getMethod("getLastModifyUser",
+					param));
+			_FrameTags.put("d", Frame.class.getMethod("getLastModifyDate",
+					param));
+			_FrameTags.put("F", Frame.class.getMethod("getFrozenDate", param));
+
+			_FrameTags.put("O", Frame.class.getMethod("getForegroundColor",
+					param));
+			_FrameTags.put("B", Frame.class.getMethod("getBackgroundColor",
+					param));
+
+			_ItemTags.put("S", Item.class.getMethod("getTypeAndID", param));
+			_ItemTags.put("s", Item.class.getMethod("getDateCreated", param));
+			_ItemTags.put("d", Item.class.getMethod("getColor", param));
+			_ItemTags.put("G", Item.class
+					.getMethod("getBackgroundColor", param));
+			_ItemTags.put("P", Item.class.getMethod("getPosition", param));
+			_ItemTags.put("F", Item.class.getMethod("getLink", param));
+			_ItemTags.put("X", Item.class.getMethod("getAction", param));
+			_ItemTags.put("x", Item.class.getMethod("getActionMark", param));
+			_ItemTags.put("U", Item.class.getMethod("getActionCursorEnter",
+					param));
+			_ItemTags.put("V", Item.class.getMethod("getActionCursorLeave",
+					param));
+			_ItemTags.put("W", Item.class.getMethod("getActionEnterFrame",
+					param));
+			_ItemTags.put("Y", Item.class.getMethod("getActionLeaveFrame",
+					param));
+			_ItemTags.put("D", Item.class.getMethod("getData", param));
+			_ItemTags.put("u", Item.class.getMethod("getHighlight", param));
+			_ItemTags.put("e", Item.class.getMethod("getFillColor", param));
+			_ItemTags.put("i", Item.class.getMethod("getFillPattern", param));
+			_ItemTags.put("o", Item.class.getMethod("getOwner", param));
+			_ItemTags.put("n", Item.class.getMethod("getLinkMark", param));
+			_ItemTags.put("q", Item.class.getMethod("getLinkFrameset", param));
+			_ItemTags.put("y", Item.class.getMethod("getLinkTemplate", param));
+			_ItemTags.put("g", Item.class.getMethod("getLinePattern", param));
+
+			_ItemTags.put("j", Item.class.getMethod("getArrow", param));
+
+			_ItemTags.put("f", Text.class.getMethod("getFont", param));
+			_ItemTags.put("t", Text.class.getMethod("getSpacing", param));
+			_ItemTags.put("T", Text.class.getMethod("getText", param));
+
+			_ItemTags.put("a", Text.class.getMethod("getWordSpacing", param));
+			_ItemTags.put("b", Text.class.getMethod("getLetterSpacing", param));
+			_ItemTags
+					.put("m", Text.class.getMethod("getInitialSpacing", param));
+			_ItemTags.put("w", Text.class.getMethod("getWidth", param));
+			_ItemTags.put("k", Text.class.getMethod("getJustification", param));
+
+			_ItemTags.put("h", Dot.class.getMethod("getThickness", param));
+			_ItemTags.put("l", Dot.class.getMethod("getLineIDs", param));
+			_ItemTags.put("c", Dot.class.getMethod("getConstraintIDs", param));
+
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void initialise(Frame start) throws IOException {
+		String name = start.getFramesetName().toLowerCase();
+
+		if (_filename == null)
+			_filename = start.path + name + File.separator
+					+ start.getFrameNumber() + ExpReader.EXTENTION;
+
+		_stringWriter = new StringBuilder();
+		if (_filename.toLowerCase().equals("clipboard")) {
+			_writer = new ProxyWriter(true);
+			_filename = "Clipboard";
+		} else
+			_writer = new ProxyWriter(new FileWriter(_filename));
+	}
+
+	/**
+	 * Writes the given Frame (and all items it contains) to a KMS file. Note:
+	 * File path and location must be set before calling this or it will do
+	 * nothing.
+	 * 
+	 * @param frame
+	 *            The Frame to write out to the file.
+	 * @throws IOException
+	 *             Any exceptions occured by the BufferedWriter.
+	 */
+	public void outputFrame(Frame frame) throws IOException {
+		if (_writer == null)
+			return;
+
+		writeHeader(frame);
+
+		// write out each Item in the Frame.
+		List<Item> items = frame.getItems();
+
+		// do not write items with ID -1, also skip any lines
+		for (Item i : items) {
+			if (i.getID() >= 0 && !(i instanceof Line))
+				writeItem(i);
+		}
+
+		// write any lines or constraints
+		writeTerminator();
+		writeLineData();
+		writeTerminator();
+		writeConstraintData();
+		
+		return;
+	}
+
+	private void writeHeader(Frame toWrite) throws IOException {
+		Iterator<String> it = _FrameTags.keySet().iterator();
+		Object[] param = {};
+		
+		while (it.hasNext()) {
+			String tag = it.next();
+			try {
+				Object o = _FrameTags.get(tag).invoke(toWrite, param);
+				o = Conversion.ConvertToKMS(_FrameTags.get(tag), o);
+				if (o != null) {
+					writeLine(tag, (String) o);
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+
+		writeTerminator();
+	}
+
+	private void writeLine(String tag, String line) throws IOException {
+		writeLine(tag + " " + line);
+	}
+
+	private void writeTerminator() throws IOException {
+		writeLine(TERMINATOR + "\n");
+	}
+
+	// writes the given line out to the file
+	private void writeLine(String line) throws IOException {
+		// do not write empty lines
+		if (line == null)
+			return;
+
+		String toWrite = line + "\n";
+
+		_writer.write(toWrite);
+		_stringWriter.append(toWrite);
+	}
+
+	// writes the given Item out to the file
+	//This method is not used to write out LINE items
+	public void writeItem(Item item) throws IOException {
+		if (_writer == null)
+			return;
+
+		// Pictures are saved as the corresponding text items that created them
+		if (item instanceof Picture)
+			item = ((Picture) item).getText();
+
+		if (item.getLines().size() > 0)
+			writePoint(item);
+		else if (!(item instanceof Line))
+			writeClass(item);
+		// lines are saved at the end of the file
+		//So dont worry about them in here
+		else
+			System.out.println("Unknown Item: " + item.getID());
+		
+		writeLine("");
+	}
+
+	private List<Item> _points = new LinkedList<Item>();
+
+	// Writes out a Dot to the file
+	protected void writePoint(Item point) throws IOException {
+		_points.add(point);
+		writeClass(point);
+	}
+
+	// writes out all lines to the file
+	private void writeLineData() throws IOException {
+		List<Line> seen = new LinkedList<Line>();
+
+		// loop through all points stored
+		for (int i = 0; i < _points.size(); i++) {
+			List<Line> lines = _points.get(i).getLines();
+
+			// if this point is part of one or more lines
+			if (lines != null && lines.size() > 0) {
+				for (Line line : lines) {
+
+					// only output new lines that have not yet been output
+					if (!seen.contains(line)) {
+						writeLine("L", line.getID() + " " + line.getLineType());
+						writeLine("s", line.getLineEnds());
+						writeLine("");
+
+						// add this line to the list of lines that have been
+						// seen
+						seen.add(line);
+					}
+				}
+			}
+		}
+	}
+
+	// writes out any constraints to the file
+	private void writeConstraintData() throws IOException {
+		// outputs any constraints the points have
+
+		// loop through all the points
+		while (_points.size() > 0) {
+			Item i = _points.get(0);
+
+			if (i instanceof Dot) {
+				Dot p = (Dot) i;
+
+				// if there are any constraints to write
+				if (p.getConstraints() != null) {
+					List<Constraint> constraints = p.getConstraints();
+
+					// do not write constraints that have already been
+					// written
+					for (Constraint c : constraints) {
+						if (_points.contains(c.getStart())
+								&& _points.contains(c.getEnd())) {
+							writeLine("C", c.getID() + " "
+									+ c.getConstraintType());
+							writeLine("s", c.getLineEnds());
+							writeLine("");
+						}
+
+					}
+				}
+			}
+			// remove the point from the list
+			_points.remove(0);
+		}
+	}
+
+	@Override
+	protected String finaliseFrame() throws IOException {
+		writeTerminator();
+
+		writeLine(SessionStats.getFrameEventList());
+
+		_writer.flush();
+		_writer.close();
+		_writer = null;
+
+		return "Frame successfully written to " + _filename;
+	}
+
+	private void writeClass(Object toWrite) throws IOException {
+		Iterator<String> it = _ItemTags.keySet().iterator();
+		Object[] param = {};
+
+		while (it.hasNext()) {
+			String tag = it.next();
+			Method toRun = _ItemTags.get(tag);
+			Class declarer = toRun.getDeclaringClass();
+			if (declarer == toWrite.getClass()
+					|| declarer == toWrite.getClass().getSuperclass()
+					|| declarer == toWrite.getClass().getSuperclass()
+							.getSuperclass()) {
+				try {
+					Object o = toRun.invoke(toWrite, param);
+					o = Conversion.ConvertToKMS(toRun, o);
+					if (o != null) {
+						if (o instanceof List) {
+							for (Object line : (List) o)
+								writeLine(tag, (String) line);
+						} else
+							writeLine(tag, (String) o);
+					}
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Gets a string representation of the frame file contents.
+	 */
+	public String getFileContents() {
+		return _stringWriter.toString();
+	}
+}
Index: /trunk/src/org/expeditee/io/FrameReader.java
===================================================================
--- /trunk/src/org/expeditee/io/FrameReader.java	(revision 6)
+++ /trunk/src/org/expeditee/io/FrameReader.java	(revision 6)
@@ -0,0 +1,11 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+
+import org.expeditee.gui.Frame;
+
+public interface FrameReader {
+
+	public abstract Frame readFrame(String fullPath)
+			throws IOException;
+}
Index: /trunk/src/org/expeditee/io/FrameWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/FrameWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/FrameWriter.java	(revision 6)
@@ -0,0 +1,18 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+
+import org.expeditee.gui.Frame;
+
+public interface FrameWriter {
+
+	public void setOutputLocation(String filename);
+
+	public String writeFrame(Frame toWrite) throws IOException;
+
+	public void stop();
+
+	public boolean isRunning();
+
+	public String getFileContents();
+}
Index: /trunk/src/org/expeditee/io/HTMLWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/HTMLWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/HTMLWriter.java	(revision 6)
@@ -0,0 +1,120 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.items.Item;
+import org.expeditee.items.Picture;
+import org.expeditee.items.Text;
+
+public class HTMLWriter extends DefaultTreeWriter {
+
+	private int _indent = 0;
+
+	private static final String INDENT = "   ";
+
+	@Override
+	protected void initialise(Frame start) throws IOException {
+		_format = ".html";
+		super.initialise(start);
+	}
+
+	@Override
+	protected void writeStartLink(Item linker) throws IOException {
+		// only output text items
+		if (!(linker instanceof Text)) {
+			_indent++;
+			return;
+		}
+
+		Text text = (Text) linker;
+		List<String> toWrite = text.getText();
+
+		String first = toWrite.get(0);
+		int ind = first.indexOf(":");
+
+		if (ind > 0) {
+			String tag = first.substring(0, ind);
+			String remain = first.substring(ind + 1);
+
+			if (remain.length() > 0) {
+				// indenting for comments
+				indent();
+				_writer.write("<!-- " + remain);
+
+				for (int i = 1; i < toWrite.size(); i++)
+					_writer.write(ItemWriter.NEW_LINE + toWrite.get(i));
+
+				_writer.write(" -->" + ItemWriter.NEW_LINE);
+			}
+
+			// indenting for tag
+			indent();
+			_writer.write("<" + tag + ">" + ItemWriter.NEW_LINE);
+		}
+
+		_indent++;
+	}
+
+	@Override
+	protected void writeEndLink(Item linker) throws IOException {
+		_indent--;
+
+		// only output text items
+		if (!(linker instanceof Text))
+			return;
+
+		Text text = (Text) linker;
+		List<String> toWrite = text.getText();
+
+		String first = toWrite.get(0);
+		int ind = first.indexOf(":");
+
+		if (ind > 0) {
+			String tag = first.substring(0, ind);
+
+			// indenting for tag
+			indent();
+			_writer.write("</" + tag + ">" + ItemWriter.NEW_LINE);
+		}
+
+	}
+
+	@Override
+	protected void writeText(Text text) throws IOException {
+		for (String s : text.getText()) {
+
+			indent();
+			// check if the first word is proceeded by a :
+			int firstColonIndex = s.indexOf(":");
+			int firstSpaceIndex = s.indexOf(" ");
+			if (firstColonIndex > 0 && firstColonIndex < firstSpaceIndex) {
+				_writer.write("<" + s.substring(0, firstColonIndex) + ">");
+				if (firstColonIndex < s.length())
+					_writer.write(s.substring(firstColonIndex + 1));
+				_writer.write("</" + s.substring(0, firstColonIndex) + ">");
+			} else
+				_writer.write(s);
+			_writer.write(ItemWriter.NEW_LINE);
+		}
+	}
+
+	private void indent() throws IOException {
+		for (int i = 0; i < _indent; i++)
+			_writer.write(INDENT);
+	}
+
+	@Override
+	protected void writePicture(Picture pic) throws IOException {
+		Text source = pic.getText();
+
+		String line = source.getFirstLine();
+		line = line.substring(line.indexOf(":") + 1).trim();
+
+		indent();
+
+		_writer.write("<img src = '../images/" + line + "'>");
+		_writer.write(ItemWriter.NEW_LINE);
+	}
+}
Index: /trunk/src/org/expeditee/io/ItemWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/ItemWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/ItemWriter.java	(revision 6)
@@ -0,0 +1,144 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Picture;
+import org.expeditee.items.Text;
+
+public abstract class ItemWriter {
+
+	public static final String NEW_LINE = System.getProperty("line.separator");
+
+	protected void writeTitle(Text toWrite, List<Item> items)
+			throws IOException {
+	}
+
+	protected void writeAnnotationTitle(Text toWrite) throws IOException {
+	}
+
+	protected void writeItem(Item toWrite) throws IOException {
+		if (toWrite.isAnnotation()) {
+			writeAnnotationItem(toWrite);
+			return;
+		}
+
+		if (toWrite instanceof Text)
+			writeText((Text) toWrite);
+
+		if (toWrite instanceof Picture)
+			writePicture((Picture) toWrite);
+
+		if (toWrite instanceof Line)
+			writeLine((Line) toWrite);
+
+		if (toWrite instanceof Dot)
+			writeDot((Dot) toWrite);
+	}
+
+	protected void writeAnnotationItem(Item toWrite) throws IOException {
+		if (toWrite instanceof Text)
+			writeAnnotationText((Text) toWrite);
+
+		if (toWrite instanceof Picture)
+			writeAnnotationPicture((Picture) toWrite);
+
+		if (toWrite instanceof Line)
+			writeAnnotationLine((Line) toWrite);
+
+		if (toWrite instanceof Dot)
+			writeAnnotationDot((Dot) toWrite);
+	}
+
+	protected void writeAnnotationText(Text toWrite) throws IOException {
+	}
+
+	protected void writeAnnotationPicture(Picture toWrite) throws IOException {
+	}
+
+	protected void writeAnnotationLine(Line toWrite) throws IOException {
+	}
+
+	protected void writeAnnotationDot(Dot toWrite) throws IOException {
+	}
+
+	protected void writeText(Text toWrite) throws IOException {
+	}
+
+	protected void writePicture(Picture toWrite) throws IOException {
+	}
+
+	protected void writeLine(Line toWrite) throws IOException {
+	}
+
+	protected void writeDot(Dot toWrite) throws IOException {
+	}
+
+	/*
+	 * public static final String NEW_LINE =
+	 * System.getProperty("line.separator");
+	 * 
+	 * protected BufferedWriter _writer = null;
+	 * 
+	 * public FrameWriter(String format){}
+	 * 
+	 * public String initialise(String path, String filename) throws
+	 * IOException{ if(path.charAt(path.length() - 1) != File.separatorChar)
+	 * path += File.separator;
+	 * 
+	 * _writer = new BufferedWriter(new FileWriter(path.toLowerCase() +
+	 * filename.toLowerCase()));
+	 * 
+	 * return filename; }
+	 * 
+	 * public void initialise(Frame toWrite) throws IOException{ String name =
+	 * toWrite.getFrameset().toLowerCase(); if(name.endsWith(".")) name =
+	 * name.substring(0, name.length() - 1);
+	 * 
+	 * String filepath = toWrite.path + name;
+	 * 
+	 * String filename = name + "." + toWrite.getFrameNumber();
+	 * 
+	 * initialise(filepath, filename); }
+	 * 
+	 * public void write(Frame toWrite) throws IOException{ initialise(toWrite);
+	 * 
+	 * writeFrame(toWrite);
+	 * 
+	 * finalise(); }
+	 * 
+	 * public void writeFrame(Frame toWrite) throws IOException{ for(Item i :
+	 * toWrite.getItems()) writeItem(i, 0); }
+	 * 
+	 * public void writeItem(Item item, int indent) throws IOException{ if(item
+	 * instanceof Text) writeText((Text) item, indent);
+	 * 
+	 * if(item instanceof Dot) writeDot((Dot) item);
+	 * 
+	 * if(item instanceof Line) writeLine((Line) item);
+	 * 
+	 * if(item instanceof Picture) writePicture((Picture) item); }
+	 * 
+	 * public void writeStartLinkItem(Item item, int indent) throws IOException{ }
+	 * 
+	 * public void writeEndLinkItem(Item item, int indent) throws IOException{ }
+	 * 
+	 * protected void writeText(Text text, int indent) throws IOException{}
+	 * 
+	 * protected void writeDot(Dot dot) throws IOException{}
+	 * 
+	 * protected void writeLine(Line line) throws IOException{}
+	 * 
+	 * protected void writePicture(Picture pic) throws IOException{}
+	 * 
+	 * public abstract void insertLine() throws IOException;
+	 * 
+	 * public abstract void indent() throws IOException;
+	 * 
+	 * public void finalise() throws IOException{ _writer.flush();
+	 * _writer.close(); _writer = null; }
+	 */
+}
Index: /trunk/src/org/expeditee/io/KMSReader.java
===================================================================
--- /trunk/src/org/expeditee/io/KMSReader.java	(revision 6)
+++ /trunk/src/org/expeditee/io/KMSReader.java	(revision 6)
@@ -0,0 +1,532 @@
+package org.expeditee.io;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Point;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.items.Constraint;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Text;
+
+/**
+ * Reads in KMS format files and constructs the Frame and Item objects they
+ * contain.
+ * 
+ * @author jdm18
+ * 
+ */
+public class KMSReader implements FrameReader {
+
+	private BufferedReader _reader = null;
+
+	private static LinkedHashMap<String, Method> _ItemTags = null;
+
+	private static LinkedHashMap<String, Method> _FrameTags = null;
+
+	/**
+	 * Does nothing, location must be set before use
+	 */
+	public KMSReader() {
+		if (_ItemTags != null && _FrameTags != null)
+			return;
+
+		_ItemTags = new LinkedHashMap<String, Method>();
+		_FrameTags = new LinkedHashMap<String, Method>();
+
+		Class[] pString = { String.class };
+		Class[] pInt = { int.class };
+		Class[] pFloat = { float.class };
+		Class[] pColor = { Color.class };
+		Class[] pBool = { boolean.class };
+		Class[] pFont = { Font.class };
+		Class[] pPoint = { Point.class };
+		Class[] pArrow = { int.class, double.class };
+		Class[] pList = { List.class };
+		Class[] pIntArray = { int[].class };
+		Class[] pItem = { Item.class };
+
+		try {
+			_FrameTags.put("A", Frame.class.getMethod("setFrameName", pString));
+			_FrameTags.put("V", Frame.class.getMethod("setVersion", pInt));
+			_FrameTags
+					.put("v", Frame.class.getMethod("setFormatVersion", pInt));
+			_FrameTags
+					.put("p", Frame.class.getMethod("setProtection", pString));
+			_FrameTags.put("U", Frame.class.getMethod("setOwner", pString));
+			_FrameTags.put("D", Frame.class
+					.getMethod("setDateCreated", pString));
+			_FrameTags.put("M", Frame.class.getMethod("setLastModifyUser",
+					pString));
+			_FrameTags.put("d", Frame.class.getMethod("setLastModifyDate",
+					pString));
+			_FrameTags
+					.put("F", Frame.class.getMethod("setFrozenDate", pString));
+
+			_FrameTags.put("O", Frame.class.getMethod("setForegroundColor",
+					pColor));
+			_FrameTags.put("B", Frame.class.getMethod("setBackgroundColor",
+					pColor));
+
+			_ItemTags.put("S", Item.class.getMethod("setID", pInt));
+			_ItemTags.put("s", Item.class.getMethod("setDateCreated", pString));
+			_ItemTags.put("d", Item.class.getMethod("setColor", pColor));
+			_ItemTags.put("G", Item.class.getMethod("setBackgroundColor",
+					pColor));
+			_ItemTags.put("P", Item.class.getMethod("setPosition", pPoint));
+			_ItemTags.put("F", Item.class.getMethod("setLink", pString));
+			_ItemTags.put("X", Item.class.getMethod("setAction", pList));
+			_ItemTags.put("x", Item.class.getMethod("setActionMark", pBool));
+			_ItemTags.put("U", Item.class.getMethod("setActionCursorEnter",
+					pList));
+			_ItemTags.put("V", Item.class.getMethod("setActionCursorLeave",
+					pList));
+			_ItemTags.put("W", Item.class.getMethod("setActionEnterFrame",
+					pList));
+			_ItemTags.put("Y", Item.class.getMethod("setActionLeaveFrame",
+					pList));
+			_ItemTags.put("D", Item.class.getMethod("setData", pString));
+			_ItemTags.put("u", Item.class.getMethod("setHighlight", pBool));
+			_ItemTags.put("e", Item.class.getMethod("setFillColor", pColor));
+			_ItemTags.put("i", Item.class.getMethod("setFillPattern", pString));
+			_ItemTags.put("o", Item.class.getMethod("setOwner", pString));
+			_ItemTags.put("n", Item.class.getMethod("setLinkMark", pBool));
+			_ItemTags
+					.put("q", Item.class.getMethod("setLinkFrameset", pString));
+			_ItemTags
+					.put("y", Item.class.getMethod("setLinkTemplate", pString));
+			_ItemTags.put("g", Item.class
+					.getMethod("setLinePattern", pIntArray));
+
+			_ItemTags.put("j", Item.class.getMethod("setArrow", pArrow));
+
+			_ItemTags.put("f", Text.class.getMethod("setFont", pFont));
+			_ItemTags.put("t", Text.class.getMethod("setSpacing", pInt));
+			_ItemTags.put("T", Text.class.getMethod("setText", pString));
+
+			_ItemTags.put("a", Text.class.getMethod("setWordSpacing", pInt));
+			_ItemTags.put("b", Text.class.getMethod("setLetterSpacing", pInt));
+			_ItemTags.put("m", Text.class.getMethod("setInitialSpacing", pInt));
+			_ItemTags.put("w", Text.class.getMethod("setWidth", pInt));
+			_ItemTags.put("k", Text.class.getMethod("setJustification", pInt));
+
+			_ItemTags.put("h", Dot.class.getMethod("setThickness", pFloat));
+			_ItemTags.put("l", Item.class.getMethod("setLineIDs", pString));
+			_ItemTags
+					.put("c", Dot.class.getMethod("setConstraintIDs", pString));
+
+			// Lines and constraints are created differently
+			_ItemTags.put("L", Line.class.getMethod("setStartItem", pItem));
+			// _ItemTags.put("g", null);
+
+			_ItemTags.put("C", Constraint.class.getMethod("getID",
+					(Class[]) null));
+			// _ItemTags.put("s2", null);
+		} catch (SecurityException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchMethodException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Determines whether a string begins with a KMS tag.
+	 * 
+	 * @param s
+	 *            a line of text
+	 * @return true if s begins with a KMS tag
+	 */
+	private boolean isValidLine(String s) {
+		return s.length() >= 3 && s.charAt(0) == '+' && s.charAt(2) == '+'
+				&& Character.isLetter(s.charAt(1));
+	}
+
+	/**
+	 * Reads a KMS file with the given name from disk.
+	 * 
+	 * @param frameName
+	 *            the name of the Frame to read in from a file.
+	 * @return A new Frame object that contains any Items described in the file.
+	 * @throws IOException
+	 *             Any exceptions occured by the BufferedReader.
+	 */
+	public Frame readFrame(String fullPath) throws IOException {
+		_reader = new BufferedReader(new FileReader(fullPath));
+		Frame newFrame = null;
+		String next = "";
+		boolean header = true;
+		// clear hashmap and line list
+		_data.clear();
+		_linePoints.clear();
+		// First read the header
+		try {
+			while (_reader.ready()) {
+				next = _reader.readLine();
+				if (isValidLine(next)) {
+					if (header) {
+						// Create the frame when the first +S+ is reached
+						// And set the flag to indicate the end of the header
+						if (getTag(next).equals("S")) {
+							newFrame = readFrameClass();
+							header = false;
+							_data.clear();
+							processBodyLine(newFrame, next);
+						} else
+							processHeaderLine(next);
+					} else
+						processBodyLine(newFrame, next);
+				}
+			}
+		} catch (Exception e) {
+			System.out.println("Error reading bodyLine: " + next + " "
+					+ e.getMessage());
+		}
+
+		// TODO add in stuff required to process stats
+		// included after the +Z+ etc..
+
+		// if the frame contains no items (Default.0 does this)
+		if (newFrame == null)
+			newFrame = readFrameClass();
+
+		_reader.close();
+
+		if (newFrame != null)
+			newFrame.setChanged(false);
+
+		return newFrame;
+	}
+
+	// stores the data of the Item to be created next
+	private LinkedHashMap<String, String> _data = new LinkedHashMap<String, String>(
+			20);
+
+	// creates an item from the last batch of data read from the file
+	private Item createItem() {
+		// creates an item based on the information in the hashmap
+		try {
+			return readItemClass();
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+
+		return null;
+	}
+
+	// Stores points used when constructing lines
+	private HashMap<Integer, Item> _linePoints = new HashMap<Integer, Item>();
+
+	/**
+	 * Adds any lines stored in _lineStarts and _lineEnds to the given Frame.
+	 * 
+	 * @param frame
+	 *            The Frame to add the newly created Line objects to.
+	 */
+	private Line createLine() {
+		String s = _data.get("L");
+		_data.remove("L");
+
+		// get the line ID and type
+		java.awt.Point idtype = separateValues(s);
+
+		// get the end points
+		s = _data.get("s");
+		_data.remove("s");
+
+		java.awt.Point startend = separateValues(s);
+
+		int start = startend.x;
+		int end = startend.y;
+
+		if (_linePoints.get(start) != null && _linePoints.get(end) != null) {
+			Line line = new Line(_linePoints.get(start), _linePoints.get(end),
+					idtype.x);
+			return line;
+		}
+
+		System.out.println(_linePoints.size());
+		for (int i : _linePoints.keySet())
+			System.out.println(i + " - "
+					+ _linePoints.get(i).getClass().getSimpleName());
+
+		System.out.println("Error: Line " + idtype.x
+				+ " has missing end point(s) + (Looking for: " + start + " , "
+				+ end + ")");
+		return null;
+	}
+
+	/*
+	 * Creates a constraint from the data containted in _data
+	 */
+	private void createConstraint() {
+		java.awt.Point idtype = separateValues(_data.get("C"));
+		java.awt.Point startend = separateValues(_data.get("s"));
+
+		Dot a = (Dot) _linePoints.get(startend.x);
+		Dot b = (Dot) _linePoints.get(startend.y);
+
+		new Constraint(a, b, idtype.x, idtype.y);
+	}
+
+	/**
+	 * Processes the body section of the KMS file, which contains all Items
+	 * 
+	 * @param frame
+	 *            The Frame to add any created Items to.
+	 * @param line
+	 *            The line of text read in from the file to process.
+	 */
+	private void processBodyLine(Frame frame, String line) {
+		// separate the tag from the value
+		String tag = getTag(line);
+		line = getValue(line);
+
+		// if this is the start of a new item, a line, or a constraint
+		// then add the last item to the frame
+		if (tag.charAt(0) == 'S' || tag.charAt(0) == 'L'
+				|| tag.charAt(0) == 'C') {
+			frame.addItem(createItem());
+
+			_data.clear();
+			_data.put(tag, line);
+			return;
+		}
+
+		// if this is the end of the file, then add the last item to the frame
+		if (tag.charAt(0) == 'Z') {
+			frame.addItem(createItem());
+			_data.clear();
+
+			return;
+		}
+
+		// check for duplicate tags (multiple lines of text)
+		String newtag = tag;
+		int i = 0;
+
+		// only executes if there are duplicate tags for this Item
+		while (_data.containsKey(newtag)) {
+			newtag = tag + i;
+			i++;
+		}
+
+		// All values are stored in a HashMap until the end of the Item is
+		// found.
+		_data.put(newtag, line);
+	}
+
+	private String getTag(String line) {
+		assert (line.charAt(0) == '+');
+		assert (line.length() > 2);
+		return line.substring(1, 2);
+	}
+
+	private String getValue(String line) {
+		assert (line.charAt(0) == '+');
+		if (line.length() > 4)
+			return line.substring(4);
+		else
+			return "";
+	}
+
+	/**
+	 * Reads the header section of the file, which contains information about
+	 * the Frame.
+	 * 
+	 * @param frame
+	 *            The Frame to assign the read values to.
+	 * @param line
+	 *            The line from the file to process.
+	 * @return False if the end of the header has been reached, True otherwise.
+	 */
+	private void processHeaderLine(String line) throws IOException {
+		// first separate the tag from the text
+		String tag = getTag(line);
+		String value = getValue(line);
+
+		if (tag.equals("Z"))
+			return;
+
+		if (_FrameTags.get(tag) == null) {
+			if (!tag.equals("t"))
+				FrameGraphics.ErrorMessage("Tag '" + tag + "' in '" + line
+						+ "' is not supported.");
+			return;
+		}
+
+		_data.put(tag, value);
+	}
+
+	// returns two ints separated by a space from the given String
+	private java.awt.Point separateValues(String line) {
+		int x = Integer.parseInt(line.substring(0, line.indexOf(" ")));
+		int y = Integer.parseInt(line.substring(line.indexOf(" ") + 1));
+
+		return new java.awt.Point(x, y);
+	}
+
+	private Frame readFrameClass() throws IOException {
+		if (_data.size() == 0) {
+			FrameGraphics.ErrorMessage("IO Error: File contains no valid KMS lines.");
+			return null;
+		}
+
+		Frame toMake = new Frame();
+		Iterator<String> it = _data.keySet().iterator();
+
+		while (it.hasNext()) {
+			String datatag = it.next();
+
+			Method toRun = _FrameTags.get(datatag);
+			Object[] vals = Conversion.Convert(toRun, _data.get(datatag));
+			try {
+				toRun.invoke(toMake, vals);
+			} catch (IllegalArgumentException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (IllegalAccessException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (InvocationTargetException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+		}
+
+		return toMake;
+	}
+
+	@SuppressWarnings("unchecked")
+	private Item readItemClass() throws IOException {
+		Iterator<String> tags = _data.keySet().iterator();
+		Class toCreate = null;
+
+		// loop through all attributes read in for this item
+		while (tags.hasNext()) {
+			String next = tags.next();
+			Method m = _ItemTags.get(next);
+			if (m != null && _ItemTags.get(next + "2") == null) {
+				if (!m.getDeclaringClass().getSimpleName().equals("Item")) {
+					toCreate = m.getDeclaringClass();
+					break;
+				}
+			}
+		}
+
+		if (toCreate == null) {
+			if (_data.size() > 0)
+				toCreate = Dot.class;
+			else
+				return null;
+		}
+
+		Item toMake = null;
+
+		try {
+			if (toCreate == Line.class)
+				toMake = createLine();
+			else if (toCreate == Constraint.class) {
+				createConstraint();
+				return null;
+			} else if (toCreate == Item.class && _data.size() == 2) {
+				toCreate = Dot.class;
+			} else if (toCreate == Item.class) {
+				for (String s : _data.keySet())
+					System.out.println(s + " -> " + _data.get(s));
+				return null;
+			} else {
+				Object[] params = { -1 };
+				Constructor con = toCreate.getConstructor(int.class);
+				toMake = (Item) con.newInstance(params);
+			}
+		} catch (InstantiationException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalAccessException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalArgumentException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvocationTargetException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (SecurityException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchMethodException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+
+		if (toMake == null)
+			return null;
+
+		Iterator<String> it = _data.keySet().iterator();
+		String last = "";
+
+		int count = 0;
+
+		while (it.hasNext()) {
+			String tag = it.next();
+			if (_data.containsKey("" + tag.charAt(0) + count)) {
+				if (last.length() == 0)
+					last = _data.get(tag);
+				else
+					last += "\n" + _data.get(tag);
+
+				count++;
+			} else {
+				Method toRun = _ItemTags.get("" + tag.charAt(0));
+				if (toRun == null)
+					System.out.println("Error accessing tag method: "
+							+ tag.charAt(0));
+				Object[] vals;
+				if (last.length() > 0)
+					vals = Conversion.Convert(toRun, last + "\n"
+							+ _data.get(tag));
+				else
+					vals = Conversion.Convert(toRun, _data.get(tag));
+
+				try {
+					if (vals != null)
+						toRun.invoke(toMake, vals);
+				} catch (IllegalArgumentException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (IllegalAccessException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (InvocationTargetException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+
+				count = 0;
+				last = "";
+			}
+
+		}
+
+		if (!(toMake instanceof Line))
+			_linePoints.put(toMake.getID(), toMake);
+
+		return toMake;
+	}
+}
Index: /trunk/src/org/expeditee/io/KMSWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/KMSWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/KMSWriter.java	(revision 6)
@@ -0,0 +1,476 @@
+package org.expeditee.io;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.items.Constraint;
+import org.expeditee.items.Dot;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Picture;
+import org.expeditee.items.Text;
+import org.expeditee.stats.SessionStats;
+
+/**
+ * Writes a Frame out to a KMS format file.
+ * 
+ * @author jdm18
+ * 
+ */
+public class KMSWriter extends DefaultFrameWriter {
+
+	private ProxyWriter _writer = null;
+
+	private StringBuilder _stringWriter = null;
+
+	// keep track of methods that are put on the same line
+	private static LinkedHashMap<String, Method> _ItemTags = null;
+
+	private static LinkedHashMap<String, Method> _FrameTags = null;
+
+	private static LinkedHashMap<String, Method> _BodyTags = null;
+
+	private static final char TERMINATOR = 'Z';
+
+	private static final char DELIMITER = '+';
+
+	public KMSWriter() {
+		if (_ItemTags != null && _FrameTags != null)
+			return;
+
+		_ItemTags = new LinkedHashMap<String, Method>();
+		_FrameTags = new LinkedHashMap<String, Method>();
+		_BodyTags = new LinkedHashMap<String, Method>();
+
+		Class[] param = {};
+
+		try {
+			_FrameTags.put("A", Frame.class.getMethod("getFrameName", param));
+			_FrameTags.put("V", Frame.class.getMethod("getVersion", param));
+			_FrameTags.put("v", Frame.class
+					.getMethod("getFormatVersion", param));
+			_FrameTags.put("p", Frame.class.getMethod("getProtection", param));
+			_FrameTags.put("U", Frame.class.getMethod("getOwner", param));
+			_FrameTags.put("D", Frame.class.getMethod("getDateCreated", param));
+			_FrameTags.put("M", Frame.class.getMethod("getLastModifyUser",
+					param));
+			_FrameTags.put("d", Frame.class.getMethod("getLastModifyDate",
+					param));
+			_FrameTags.put("F", Frame.class.getMethod("getFrozenDate", param));
+
+			_BodyTags.put("O", Frame.class.getMethod("getForegroundColor",
+					param));
+			_BodyTags.put("B", Frame.class.getMethod("getBackgroundColor",
+					param));
+			_BodyTags
+					.put("v", Frame.class.getMethod("getFormatVersion", param));
+
+			_ItemTags.put("S", Item.class.getMethod("getID", param));
+			_ItemTags.put("s", Item.class.getMethod("getDateCreated", param));
+			_ItemTags.put("d", Item.class.getMethod("getColor", param));
+			_ItemTags.put("G", Item.class
+					.getMethod("getBackgroundColor", param));
+			_ItemTags.put("P", Item.class.getMethod("getPosition", param));
+			_ItemTags.put("F", Item.class.getMethod("getLink", param));
+			_ItemTags.put("X", Item.class.getMethod("getAction", param));
+			_ItemTags.put("x", Item.class.getMethod("getActionMark", param));
+			_ItemTags.put("U", Item.class.getMethod("getActionCursorEnter",
+					param));
+			_ItemTags.put("V", Item.class.getMethod("getActionCursorLeave",
+					param));
+			_ItemTags.put("W", Item.class.getMethod("getActionEnterFrame",
+					param));
+			_ItemTags.put("Y", Item.class.getMethod("getActionLeaveFrame",
+					param));
+			_ItemTags.put("D", Item.class.getMethod("getData", param));
+			_ItemTags.put("u", Item.class.getMethod("getHighlight", param));
+			_ItemTags.put("e", Item.class.getMethod("getFillColor", param));
+			_ItemTags.put("i", Item.class.getMethod("getFillPattern", param));
+			_ItemTags.put("o", Item.class.getMethod("getOwner", param));
+			_ItemTags.put("n", Item.class.getMethod("getLinkMark", param));
+			_ItemTags.put("q", Item.class.getMethod("getLinkFrameset", param));
+			_ItemTags.put("y", Item.class.getMethod("getLinkTemplate", param));
+			_ItemTags.put("g", Item.class.getMethod("getLinePattern", param));
+
+			_ItemTags.put("j", Item.class.getMethod("getArrow", param));
+
+			_ItemTags.put("f", Text.class.getMethod("getFont", param));
+			_ItemTags.put("t", Text.class.getMethod("getSpacing", param));
+			_ItemTags.put("T", Text.class.getMethod("getText", param));
+
+			_ItemTags.put("a", Text.class.getMethod("getWordSpacing", param));
+			_ItemTags.put("b", Text.class.getMethod("getLetterSpacing", param));
+			_ItemTags
+					.put("m", Text.class.getMethod("getInitialSpacing", param));
+			_ItemTags.put("w", Text.class.getMethod("getWidth", param));
+			_ItemTags.put("k", Text.class.getMethod("getJustification", param));
+
+			_ItemTags.put("h", Dot.class.getMethod("getThickness", param));
+			_ItemTags.put("l", Dot.class.getMethod("getLineIDs", param));
+			_ItemTags.put("c", Dot.class.getMethod("getConstraintIDs", param));
+
+			// Lines and constraints are created differently
+			/*
+			 * _LineTags.put("L", Line.class.getMethod("getID", param));
+			 * _LineTags.put("s", Line.class.getMethod("getLineEnds", param));
+			 * _LineTags.put("g", Line.class.getMethod("getLinePattern",
+			 * param));
+			 * 
+			 * _ConstraintTags.put("C", Constraint.class.getMethod("getID",
+			 * (Class[]) null)); _ConstraintTags.put("s",
+			 * Constraint.class.getMethod("getLineEnds", (Class[]) null));
+			 */
+		} catch (SecurityException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchMethodException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	public void initialise(Frame start) throws IOException {
+		String name = start.getFramesetName().toLowerCase();
+
+		if (_filename == null)
+			_filename = start.path + name + File.separator + name + "."
+					+ start.getFrameNumber();
+
+		_stringWriter = new StringBuilder();
+		if (_filename.toLowerCase().equals("clipboard")) {
+			_writer = new ProxyWriter(true);
+			_filename = "Clipboard";
+		} else
+			_writer = new ProxyWriter(new FileWriter(_filename));
+
+		// _writer = new BufferedWriter(new FileWriter(filepath.toLowerCase() +
+		// filename.toLowerCase()));
+	}
+
+	/**
+	 * Writes the given Frame (and all items it contains) to a KMS file. Note:
+	 * File path and location must be set before calling this or it will do
+	 * nothing.
+	 * 
+	 * @param frame
+	 *            The Frame to write out to the file.
+	 * @throws IOException
+	 *             Any exceptions occured by the BufferedWriter.
+	 */
+	public void outputFrame(Frame frame) throws IOException {
+		if (_writer == null)
+			return;
+
+		writeHeader(frame);
+		// writeBody(frame);
+
+		// write out each Item in the Frame.
+		List<Item> items = frame.getItems();
+
+		// do not write items with ID -1, also skip any lines
+		for (Item i : items) {
+			if (i.getID() >= 0 && !(i instanceof Line))
+				writeItem(i);
+		}
+
+		// write any lines or constraints
+		writeLineData();
+		writeConstraintData();
+
+		return;
+	}
+
+	private void writeHeader(Frame toWrite) throws IOException {
+		Iterator<String> it = _FrameTags.keySet().iterator();
+		Object[] param = {};
+
+		while (it.hasNext()) {
+			String tag = it.next();
+			try {
+				Object o = _FrameTags.get(tag).invoke(toWrite, param);
+				o = Conversion.ConvertToKMS(_FrameTags.get(tag), o);
+				if (o != null) {
+					writeLine(tag, (String) o);
+				}
+			} catch (IllegalArgumentException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (IllegalAccessException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (InvocationTargetException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+		}
+
+		writeTerminator();
+		it = _BodyTags.keySet().iterator();
+
+		while (it.hasNext()) {
+			String tag = it.next();
+			try {
+				Object o = _BodyTags.get(tag).invoke(toWrite, param);
+				o = Conversion.ConvertToKMS(_BodyTags.get(tag), o);
+				if (o != null) {
+					writeLine(tag, (String) o);
+				}
+			} catch (IllegalArgumentException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (IllegalAccessException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (InvocationTargetException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+		}
+	}
+
+	private void writeLine(String tag, String line) throws IOException {
+		writeLine(DELIMITER + tag + DELIMITER + " " + line);
+	}
+
+	private void writeTerminator() throws IOException {
+		writeLine("" + DELIMITER + TERMINATOR + DELIMITER);
+	}
+
+	// writes the given line out to the file
+	private void writeLine(String line) throws IOException {
+		// do not write empty lines
+		if (line == null)
+			return;
+
+		String toWrite = line + "\n";
+
+		_writer.write(toWrite);
+		_stringWriter.append(toWrite);
+	}
+
+	// writes the given Item out to the file
+	public void writeItem(Item item) throws IOException {
+		if (_writer == null)
+			return;
+
+		writeLine("");
+		// Pictures are saved as the corresponding text items that created them
+		if (item instanceof Picture)
+			item = ((Picture) item).getText();
+
+		/*
+		 * if(item instanceof Text) writeClass(item); else if(item instanceof
+		 * Dot) writeDot((Dot) item);
+		 */
+		if (item.getLines().size() > 0)
+			writePoint(item);
+		else if (!(item instanceof Line))
+			writeClass(item);
+		// lines are saved at the end of the file
+		/*
+		 * else if (item instanceof Line) ;
+		 */
+		else
+			System.out.println("Unknown Item: " + item.getID());
+	}
+
+	private List<Item> _points = new LinkedList<Item>();
+
+	// Writes out a Dot to the file
+	protected void writePoint(Item point) throws IOException {
+		_points.add(point);
+		writeClass(point);
+	}
+
+	// writes out all lines to the file
+	private void writeLineData() throws IOException {
+		List<Line> seen = new LinkedList<Line>();
+
+		// loop through all points stored
+		for (int i = 0; i < _points.size(); i++) {
+			List<Line> lines = _points.get(i).getLines();
+
+			// if this point is part of one or more lines
+			if (lines != null && lines.size() > 0) {
+				for (Line line : lines) {
+
+					// only output new lines that have not yet been output
+					if (!seen.contains(line)) {
+						writeLine("L", line.getID() + " " + line.getLineType());
+						writeLine("s", line.getLineEnds());
+
+						// add this line to the list of lines that have been
+						// seen
+						seen.add(line);
+					}
+
+				}
+			}
+		}
+	}
+
+	// writes out any constraints to the file
+	private void writeConstraintData() throws IOException {
+		// outputs any constraints the points have
+
+		// loop through all the points
+		while (_points.size() > 0) {
+			Item i = _points.get(0);
+
+			if (i instanceof Dot) {
+				Dot p = (Dot) i;
+
+				// if there are any constraints to write
+				if (p.getConstraints() != null) {
+					List<Constraint> constraints = p.getConstraints();
+
+					// do not write constraints that have already been
+					// written
+					for (Constraint c : constraints) {
+						if (_points.contains(c.getStart())
+								&& _points.contains(c.getEnd())) {
+							writeLine("C", c.getID() + " "
+									+ c.getConstraintType());
+							writeLine("s", c.getLineEnds());
+						}
+
+					}
+				}
+			}
+			// remove the point from the list
+			_points.remove(0);
+
+		}
+	}
+
+	@Override
+	protected String finaliseFrame() throws IOException {
+		writeLine("" + DELIMITER + TERMINATOR + DELIMITER);
+
+		writeLine(SessionStats.getFrameEventList());
+
+		_writer.flush();
+		_writer.close();
+		_writer = null;
+
+		return "Frame successfully written to " + _filename;
+	}
+
+	private void writeClass(Object toWrite) throws IOException {
+		Iterator<String> it = _ItemTags.keySet().iterator();
+		Object[] param = {};
+
+		while (it.hasNext()) {
+			String tag = it.next();
+			Method toRun = _ItemTags.get(tag);
+			Class declarer = toRun.getDeclaringClass();
+			if (declarer == toWrite.getClass()
+					|| declarer == toWrite.getClass().getSuperclass()
+					|| declarer == toWrite.getClass().getSuperclass()
+							.getSuperclass()) {
+				try {
+					Object o = toRun.invoke(toWrite, param);
+					o = Conversion.ConvertToKMS(toRun, o);
+					if (o != null) {
+						if (o instanceof List) {
+							for (Object line : (List) o)
+								writeLine(tag, (String) line);
+						} else
+							writeLine(tag, (String) o);
+					}
+				} catch (IllegalArgumentException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (IllegalAccessException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (InvocationTargetException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+
+		/*
+		 * Method[] methods = toWrite.getClass().getMethods(); ArrayList<String>
+		 * meths = new ArrayList<String>();
+		 * 
+		 * for(Method m : methods) meths.add(m.getName());
+		 * 
+		 * String start = _Tags.get("DelimiterStart"); String end =
+		 * _Tags.get("DelimiterEnd");
+		 * 
+		 * Iterator<String> it = _Tags.keySet().iterator();
+		 * 
+		 * String term = null; LinkedList<Method> seen = new LinkedList<Method>();
+		 * 
+		 * while(it.hasNext()){ String name = it.next(); String cName =
+		 * className;
+		 * 
+		 * if(writeSuper && name.startsWith(superName)) cName = superName;
+		 * 
+		 * if(name.startsWith(cName)){ String tag = start + _Tags.get(name) +
+		 * end; String output = "";
+		 * 
+		 * //check for other methods that go on this line LinkedList<String>
+		 * shared = _SharedLine.get(name); LinkedList<String> runMethods = new
+		 * LinkedList<String>();
+		 * 
+		 * if(shared != null) runMethods.addAll(shared);
+		 * 
+		 * name = name.substring((cName + ".").length());
+		 * runMethods.addFirst(name);
+		 * 
+		 * //loop through all methods and concat their output Iterator
+		 * methodIterator = runMethods.iterator();
+		 * 
+		 * while(methodIterator.hasNext()){ name = "get" +
+		 * methodIterator.next();
+		 * 
+		 * int ind = meths.indexOf(name);
+		 * 
+		 * if(ind >= 0){ Method m = methods[ind];
+		 * 
+		 * if(!seen.contains(m)){ try { if(m.getParameterTypes().length == 0){
+		 * Object val = m.invoke(toWrite, (Object[]) null);
+		 * 
+		 * if(val instanceof List){ int count = 0; for(Object o : (List) val){
+		 * if(count == 0) output += " " +
+		 * KMSConversion.ConvertToKMS(name.substring(3), o); else output += "\n" +
+		 * tag + " " + KMSConversion.ConvertToKMS(name.substring(3), o);
+		 * 
+		 * count++; } }else{ String res =
+		 * KMSConversion.ConvertToKMS(name.substring(3), val);
+		 * 
+		 * if(res != null) output += " " + res; } } } catch (Exception e) {
+		 * Logger.Log(e); }
+		 * 
+		 * seen.add(m); } }else if(name.equals("getTerminator")){ term = start +
+		 * _Tags.get(cName + ".Terminator") + end; }else System.out.println("No
+		 * method found: " + cName + "." + name); }
+		 * 
+		 * if(output.length() > 0) writeLine(tag + output); } } if(term != null)
+		 * writeLine(term);
+		 */
+	}
+
+	/*
+	 * private void writeBody(Object toWrite) throws IOException{
+	 * writeClass(toWrite, "Body", " ", false); }
+	 */
+
+	/**
+	 * Gets a string representation of the frame file contents.
+	 */
+	public String getFileContents() {
+		return _stringWriter.toString();
+	}
+}
Index: /trunk/src/org/expeditee/io/Logger.java
===================================================================
--- /trunk/src/org/expeditee/io/Logger.java	(revision 6)
+++ /trunk/src/org/expeditee/io/Logger.java	(revision 6)
@@ -0,0 +1,180 @@
+package org.expeditee.io;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.expeditee.gui.TimeKeeper;
+
+/**
+ * This class is used to create all log files. The log file is opened, appended,
+ * and closed for each new entry to ensure robustness against program crashes.
+ * Files are created with filename: UserData.Date.Time<br>
+ * Where Date is in the format DDMMMYYYY and Time is in the format HHMM. <br>
+ * <br>
+ * Log files are stored in the following format: <br>
+ * <code>
+ * TIME: (Time since program start) <br>
+ * X,Y (For user events only) <br> 
+ * Origin (System/User) <br>
+ * Description <br>
+ * </code> <br>
+ * Exceptions can also be logged, in which case the result from
+ * Exception.printStackTrace() is output to the file.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Logger {
+	public static final String SYSTEM = "S";
+
+	public static final String USER = "U";
+
+	public static final String LOAD = "Load";
+
+	public static final String SAVE = "Save";
+
+	public static final String PICKUP = "Pickup";
+
+	public static final String ANCHOR = "Anchor";
+
+	public static final String DELETE = "Delete";
+
+	public static final String TDFC = "TDFC";
+
+	public static final String NEW_FRAMESET = "New Frameset";
+
+	public static final String PICKUP_COPY = "Pickup Copies";
+
+	public static final String ANCHOR_COPY = "Anchor Copies";
+
+	public static final String CONTROL_CHAR = "Control Character";
+
+	public static final String CREATE_TEXT = "Creating Text";
+
+	public static final String INSERTING_TEXT = "Inserting Text";
+
+	public static final String BACK = "Back";
+
+	public static final String FOLLOW_LINK = "Following Link";
+
+	public static final String MERGING = "Merging";
+
+	// These should be the same as used in KMSReader
+	private static String _filename = null;
+
+	// how many spaces the output time should be padded to
+	private static int _padTime = 8;
+
+	private static TimeKeeper _timer = new TimeKeeper();
+
+	/**
+	 * Sets the path on disk that the log files will be created in
+	 * 
+	 * @param location
+	 *            The path on disk to create the log files in.
+	 */
+	public static void Init() {
+		if (!org.expeditee.gui.UserSettings.Logging)
+			return;
+
+		File test = new File(org.expeditee.gui.FrameIO.LOGS_DIR);
+		if (!test.exists())
+			test.mkdir();
+
+		_filename = "UserData-" + EasyDateFormat("ddMMMyy-HHmm") + ".txt";
+	}
+
+	/**
+	 * Appends the given event to the log file.
+	 * 
+	 * @param origin
+	 *            The origin of this event, typically SYSTEM
+	 * @param type
+	 *            The Type of this event (LOAD, SAVE, CLICK, etc)
+	 * @param comments
+	 *            Any further comments that should be recorded about the event
+	 */
+	public static void Log(String origin, String type, String comments) {
+		String toWrite = "TIME:\t" + _timer.getElapsedStringMillis(_padTime)
+				+ " \t\t\t\t" + " - " + origin + " - " + type + " - "
+				+ comments;
+		WriteToFile(toWrite);
+	}
+
+	/**
+	 * Appends the given event to the log file.
+	 * 
+	 * @param x
+	 *            The mouse X coordinate when the event occured
+	 * @param y
+	 *            The mouse Y coordinate when the event occured
+	 * @param origin
+	 *            The origin of the event, typically USER
+	 * @param type
+	 *            The type of this event (LOAD, SAVE, CLICK, etc).
+	 * @param comments
+	 *            Any further comments that should be recorded about the event
+	 */
+	public static void Log(int x, int y, String origin, String type,
+			String comments) {
+		String toWrite = "TIME:\t" + _timer.getElapsedStringMillis(_padTime)
+				+ " \t X:" + x + " Y:" + y + " - " + origin + " - " + type
+				+ " - " + comments;
+		WriteToFile(toWrite);
+	}
+
+	/**
+	 * Appends a record of the Exception to the log file. The text output is
+	 * equal to that output by e.printStackTrace()
+	 * 
+	 * @param e
+	 *            The Exception to log.
+	 */
+	public static void Log(Exception e) {
+		String toWrite = "TIME:" + _timer.getElapsedStringMillis(_padTime)
+				+ "\tException Thrown: " + e.getClass().getSimpleName();
+
+		WriteToFile(toWrite);
+
+		StackTraceElement[] st = e.getStackTrace();
+
+		for (StackTraceElement element : st)
+			WriteToFile(element.toString());
+
+	}
+
+	public static String EasyDateFormat(String format, Date date) {
+		return (new SimpleDateFormat(format)).format(date);
+	}
+
+	public static String EasyDateFormat(String format) {
+		return EasyDateFormat(format, new Date());
+	}
+
+	private static void WriteToFile(String toWrite) {
+		if (!org.expeditee.gui.UserSettings.Logging)
+			return;
+
+		// if Init has not been run yet
+		if (_filename == null)
+			Init();
+
+		try {
+			BufferedWriter writer = new BufferedWriter(new FileWriter(
+					org.expeditee.gui.FrameIO.LOGS_DIR + _filename, true));
+			writer.write(toWrite);
+			writer.newLine();
+
+			writer.flush();
+			writer.close();
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+		}
+
+	}
+
+}
Index: /trunk/src/org/expeditee/io/ProxyWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/ProxyWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/ProxyWriter.java	(revision 6)
@@ -0,0 +1,52 @@
+package org.expeditee.io;
+
+import java.awt.Toolkit;
+import java.awt.datatransfer.StringSelection;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.PipedWriter;
+import java.io.Writer;
+
+public class ProxyWriter extends BufferedWriter {
+
+	private StringBuffer _contents = null;
+
+	public ProxyWriter(Writer out) {
+		super(out);
+	}
+
+	public ProxyWriter(Writer out, int sz) {
+		super(out, sz);
+	}
+
+	public ProxyWriter(boolean useClipboard) {
+		// create a writer that does nothing
+		super(new PipedWriter());
+		_contents = new StringBuffer("");
+	}
+
+	@Override
+	public void write(String s) throws IOException {
+		if (_contents != null) {
+			_contents.append(s);
+		} else
+			super.write(s);
+	}
+
+	@Override
+	public void flush() throws IOException {
+		if (_contents != null) {
+			StringSelection selection = new StringSelection(_contents
+					.toString());
+			Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
+					selection, null);
+		} else
+			super.flush();
+	}
+
+	@Override
+	public void close() throws IOException {
+		if (_contents == null)
+			super.close();
+	}
+}
Index: /trunk/src/org/expeditee/io/TEXWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/TEXWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/TEXWriter.java	(revision 6)
@@ -0,0 +1,97 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Text;
+
+public class TEXWriter extends DefaultTreeWriter {
+
+	// may be needed for sectioning commands
+	private Text _title = null;
+
+	@Override
+	protected void initialise(Frame start) throws IOException {
+		_format = ".tex";
+		super.initialise(start);
+	}
+
+	protected void writeTitle(Text title, List<Item> items) throws IOException {
+		_title = title;
+
+		Text text = ItemUtils.FindTag(items, "@TexSection");
+
+		if (text != null) {
+			String first = text.getFirstLine();
+			int ind = first.indexOf(":");
+
+			if (ind > 0) {
+				String command = first.substring(ind + 1).trim().toLowerCase();
+
+				_writer.write("\\" + command + "{");
+
+				List<String> titleLines = _title.getText();
+				for (int i = 0; i < titleLines.size() - 1; i++) {
+					_writer.write(titleLines.get(i));
+					_writer.write(ItemWriter.NEW_LINE);
+				}
+
+				_writer.write(titleLines.get(titleLines.size() - 1));
+
+				_writer.write("}" + ItemWriter.NEW_LINE);
+			}
+		}
+	}
+
+	@Override
+	protected void writeStartLink(Item linker) throws IOException {
+		// only output text items
+		if (!(linker instanceof Text))
+			return;
+
+		Text text = (Text) linker;
+		List<String> toWrite = text.getText();
+
+		String first = toWrite.get(0);
+		int ind = first.indexOf(":");
+
+		if (ind > 0) {
+			String tag = first.substring(0, ind);
+			_writer.write("\\" + tag + "{" + ItemWriter.NEW_LINE);
+		}
+	}
+
+	@Override
+	protected void writeEndLink(Item linker) throws IOException {
+		// only output text items
+		if (!(linker instanceof Text))
+			return;
+
+		Text text = (Text) linker;
+		List<String> toWrite = text.getText();
+
+		String first = toWrite.get(0);
+		int ind = first.indexOf(":");
+
+		if (ind > 0) {
+			_writer.write("}" + ItemWriter.NEW_LINE);
+		}
+
+	}
+
+	@Override
+	protected void writeText(Text text) throws IOException {
+		for (String s : text.getText()) {
+			_writer.write(s);
+			_writer.write(ItemWriter.NEW_LINE);
+		}
+	}
+
+	@Override
+	protected void writeAnnotationText(Text text) throws IOException {
+
+	}
+}
Index: /trunk/src/org/expeditee/io/TXTWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/TXTWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/TXTWriter.java	(revision 6)
@@ -0,0 +1,91 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Text;
+
+public class TXTWriter extends DefaultTreeWriter implements FrameWriter {
+
+	private boolean _join = false;
+
+	private int _indent = 0;
+
+	@Override
+	protected void initialise(Frame start) throws IOException {
+		_format = ".txt";
+		super.initialise(start);
+	}
+
+	@Override
+	protected void writeStartFrame(Frame toParse) throws IOException {
+		if (ItemUtils.ContainsTag(toParse.getItems(), "@join"))
+			_join = !_join;
+
+		if (ItemUtils.ContainsTag(toParse.getItems(), "@indent"))
+			_indent++;
+
+		super.writeStartFrame(toParse);
+	}
+
+	@Override
+	protected void writeTitle(Text title, List<Item> items) throws IOException {
+		int indent = 0;
+
+		if (_indent > 0)
+			indent = _indent - 1;
+
+		for (String s : title.getText()) {
+
+			for (int i = 0; i < indent; i++)
+				_writer.write("\t");
+
+			_writer.write(s);
+			_writer.write(ItemWriter.NEW_LINE);
+		}
+
+		if (!_join)
+			_writer.write(ItemWriter.NEW_LINE);
+	}
+
+	@Override
+	protected void writeEndFrame(Frame toParse) throws IOException {
+		if (ItemUtils.ContainsTag(toParse.getItems(), "@indent"))
+			if (_indent > 0)
+				_indent--;
+
+		if (ItemUtils.ContainsTag(toParse.getItems(), "@join"))
+			_join = !_join;
+
+		_writer.write(ItemWriter.NEW_LINE);
+	}
+
+	@Override
+	protected void resumeFrame(Frame resuming) {
+		_join = ItemUtils.ContainsTag(resuming.getItems(), "@join");
+	}
+
+	@Override
+	protected void writeText(Text text) throws IOException {
+		for (String s : text.getText()) {
+
+			for (int i = 0; i < _indent; i++)
+				_writer.write("\t");
+
+			_writer.write(s);
+			_writer.write(ItemWriter.NEW_LINE);
+		}
+
+		if (!_join)
+			_writer.write(ItemWriter.NEW_LINE);
+	}
+
+	@Override
+	protected void writeAnnotationText(Text toWrite) throws IOException {
+		if (toWrite.startsWith("@BlankLine"))
+			_writer.write(ItemWriter.NEW_LINE);
+	}
+}
Index: /trunk/src/org/expeditee/io/TreeWriter.java
===================================================================
--- /trunk/src/org/expeditee/io/TreeWriter.java	(revision 6)
+++ /trunk/src/org/expeditee/io/TreeWriter.java	(revision 6)
@@ -0,0 +1,10 @@
+package org.expeditee.io;
+
+import java.io.IOException;
+
+import org.expeditee.gui.Frame;
+
+public interface TreeWriter extends FrameWriter {
+
+	public String writeTree(Frame toWrite) throws IOException;
+}
Index: /trunk/src/org/expeditee/items/Constraint.java
===================================================================
--- /trunk/src/org/expeditee/items/Constraint.java	(revision 6)
+++ /trunk/src/org/expeditee/items/Constraint.java	(revision 6)
@@ -0,0 +1,119 @@
+package org.expeditee.items;
+
+/**
+ * This class represents a constraint between two Dots on the screen. A vertical
+ * constraint means the Dot's Y values will be equal, likewise a horizontal
+ * constraint means the Dot's X values will be equal.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Constraint {
+	public static final int VERTICAL = 2;
+
+	public static final int HORIZONTAL = 3;
+
+	// the list of points that are constrained
+	private Item _start = null;
+
+	private Item _end = null;
+
+	// id and type info
+	private int _id;
+
+	private int _type;
+
+	/**
+	 * Constructs a constraint with the two given end points, id, and type. Type
+	 * should be one of the constants defined in this class.
+	 * 
+	 * @param a
+	 *            One of the points in this constraint.
+	 * @param b
+	 *            The other point in this constraint.
+	 * @param id
+	 *            The ID of this constraint item.
+	 * @param type
+	 *            The type of this constraint (horizontal, vertical).
+	 */
+	public Constraint(Item a, Item b, int id, int type) {
+		_start = a;
+		_end = b;
+
+		a.addConstraint(this);
+		b.addConstraint(this);
+
+		_id = id;
+		_type = type;
+	}
+
+	/**
+	 * @return The Item ID of this Constraint.
+	 */
+	public int getID() {
+		return _id;
+	}
+
+	/**
+	 * 
+	 * @return The type of this Constraint. The Type corresponds to the
+	 *         constants defined in this class.
+	 */
+	public int getConstraintType() {
+		return _type;
+	}
+
+	public Item getStart() {
+		return _start;
+	}
+
+	public Item getEnd() {
+		return _end;
+	}
+
+	public boolean contains(Item d) {
+		if (_start == d || _end == d)
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * If there are only two points in this constraint, this method returns the
+	 * Point not passed to it. If there are more than two points then the first
+	 * encountered is returned.
+	 * 
+	 * @return A Point in the constraint that is not the one given (or null if
+	 *         none is found).
+	 */
+	public Item getOppositeEnd(Item from) {
+		if (from == _start)
+			return _end;
+
+		if (from == _end)
+			return _start;
+
+		return null;
+	}
+
+	public void replacePoint(Dot toReplace, Dot with) {
+		if (_start == toReplace)
+			_start = with;
+
+		if (_end == toReplace)
+			_end = with;
+	}
+
+	public String getLineEnds() {
+		String ends = "";
+
+		if (_start != null)
+			ends += _start.getID();
+
+		if (_end != null)
+			ends += " " + _end.getID();
+
+		return ends;
+	}
+
+}
Index: /trunk/src/org/expeditee/items/Dot.java
===================================================================
--- /trunk/src/org/expeditee/items/Dot.java	(revision 6)
+++ /trunk/src/org/expeditee/items/Dot.java	(revision 6)
@@ -0,0 +1,348 @@
+package org.expeditee.items;
+
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Represents a point on the screen. All Point objects are stored as x,y pairs
+ * with an Item ID. Note: Lines and all combinates of lines (rectangles, etc)
+ * are represented only as Points that share line IDs.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Dot extends Item {
+
+	// Standard Item variables
+
+	// contains all dots (including this one) that form an enclosure
+	// if this dot is part of an enclosing shape
+	private List<Dot> _enclosure = null;
+
+	// the polygon surrounding this point, used for 'gravity'
+	private Polygon _poly = null;
+
+	// The thickness (in pixels) of this Point
+	private float _thickness = -1;
+
+	private int _pointType = Item.POINTTYPE_SQUARE;
+
+	private boolean _filled = true;
+
+	public Dot(int id) {
+		super();
+		setID(id);
+	}
+
+	/**
+	 * Constructs a new Point with the given x,y coordinates and Item ID.
+	 * 
+	 * @param x
+	 *            The x coordinate of this point
+	 * @param y
+	 *            The y coordinate of this point
+	 * @param id
+	 *            The Item ID of this Point
+	 */
+	public Dot(int x, int y, int id) {
+		super();
+		setID(id);
+		setPosition(x, y);
+	}
+
+	public void setPointType(int type) {
+		_pointType = type;
+	}
+
+	public void useFilledPoints(boolean val) {
+		_filled = val;
+	}
+
+	@Override
+	protected boolean hasVisibleArrow() {
+		return getArrowheadRatio() != 0 && getArrowheadLength() != 0;
+	}
+
+	/**
+	 * Sets the 'thickness' of this Dot, that is, how many pixels this Dot
+	 * should be.
+	 * 
+	 * @param thick
+	 *            The number of pixels to use when displaying this Dot The Dot
+	 *            is displayed as a thick x thick square.
+	 */
+	@Override
+	public void setThickness(float thick) {
+		_thickness = thick;
+
+		updatePolygon();
+
+		// update the size of any lines
+		for (Line line : getLines())
+			line.setThickness(thick);
+	}
+
+	/**
+	 * Returns the thickness (in pixels) of this Dot.
+	 * 
+	 * @return The 'thickness' of this Dot. (returns -1 if the thickness is not
+	 *         set).
+	 */
+	@Override
+	public float getThickness() {
+		return _thickness;
+	}
+
+	public void paintFill(Graphics2D g) {
+		if (getFillColor() != null && getEnclosingDots() != null) {
+			g.setColor(getFillColor());
+			g.fillPolygon(getEnclosedShape());
+		}
+	}
+
+	@Override
+	public void paint(Graphics2D g) {
+		if (isHighlighted()) {
+			g.setColor(getHighlightColor());
+			g.drawPolygon(getPolygon());
+		}
+
+		// dots on lines are hidden
+		if (getLines().size() > 0)
+			return;
+
+		g.setColor(getPaintColor());
+
+		int thick = (int) _thickness;
+		if (thick < 2)
+			thick = 2;
+
+		int width = thick / 2;
+
+		// if(_thickness < 1)
+		/*
+		 * if(_filled) g.fillRect(getX(), getY(), thick,thick); else
+		 * g.drawRect(getX(), getY(), thick,thick); else
+		 */
+
+		if (_pointType == Item.POINTTYPE_CIRCLE) {
+			int points = 40;
+
+			double radians = 0.0;
+			int xPoints[] = new int[points];
+			int yPoints[] = new int[xPoints.length];
+
+			for (int i = 0; i < xPoints.length; i++) {
+				xPoints[i] = getX() + (int) (width * Math.cos(radians));
+				yPoints[i] = getY() + (int) (width * Math.sin(radians));
+				radians += (2.0 * Math.PI) / xPoints.length;
+			}
+
+			if (_filled)
+				g.fillPolygon(new Polygon(xPoints, yPoints, xPoints.length));
+			else
+				g.drawPolygon(new Polygon(xPoints, yPoints, xPoints.length));
+		} else {
+			if (_filled)
+				g.fillRect(getX() - width, getY() - width, thick, thick);
+			else
+				g.drawRect(getX() - width, getY() - width, thick, thick);
+		}
+
+	}
+
+	/**
+	 * Updates the points of the polygon surrounding this Dot
+	 */
+	protected void updatePolygon() {
+		int thick = (int) Math.ceil(getThickness());
+		// Sets a minimum size for the dot
+		thick = Math.max(thick, 10);
+
+		int x = getX() - thick / 2;
+		int y = getY() - thick / 2;
+
+		_poly = new Polygon();
+		_poly.addPoint(x - getGravity(), y - getGravity());
+		_poly.addPoint(x + thick + getGravity(), y - getGravity());
+		_poly.addPoint(x + thick + getGravity(), y + thick + getGravity());
+		_poly.addPoint(x - getGravity(), y + thick + getGravity());
+	}
+
+	@Override
+	public Polygon getPolygon() {
+		if (_poly == null)
+			updatePolygon();
+
+		Polygon external = new Polygon(_poly.xpoints, _poly.ypoints,
+				_poly.npoints);
+
+		return external;
+	}
+
+	@Override
+	public Dot copy() {
+		Dot copy = new Dot(getX(), getY(), getID());
+
+		Item.DuplicateItem(this, copy);
+
+		copy.setThickness(getThickness());
+		copy.setPointType(_pointType);
+		copy.useFilledPoints(_filled);
+
+		return copy;
+	}
+
+	@Override
+	public int showHighlight(boolean val) {
+		super.showHighlight(val);
+
+		return Item.DEFAULT_CURSOR;
+	}
+
+	@Override
+	public int getSize() {
+		return (int) getThickness();
+	}
+
+	@Override
+	public void setSize(int size) {
+	}
+
+	@Override
+	public void setAnnotation(boolean val) {
+	}
+
+	@Override
+	public boolean isAnnotation() {
+		return false;
+	}
+
+	@Override
+	public Item merge(Item merger, int mouseX, int mouseY) {
+		// if the item being merged with is another Dot
+		if (merger instanceof Dot) {
+			Dot dot = (Dot) merger;
+
+			// prevent concurrency issues if removing lines
+			List<Line> lines = new ArrayList<Line>();
+			lines.addAll(dot.getLines());
+
+			for (Line line : lines) {
+				// remove lines that are in common
+				if (getLines().contains(line)) {
+					dot.removeLine(line);
+					removeLine(line);
+				} else {
+					// check for duplicated lines as a result of merging
+					Dot check = (Dot) line.getOppositeEnd(dot);
+					boolean dup = false;
+
+					for (Line l : getLines()) {
+						Item opposite = l.getOppositeEnd(this);
+
+						if (check == opposite) {
+							line.getStartItem().removeLine(line);
+							line.getEndItem().removeLine(line);
+							dup = true;
+							break;
+						}
+					}
+
+					if (!dup)
+						line.replaceEnd(dot, this);
+				}
+			}
+
+			setThickness(dot.getThickness());
+			setColor(dot.getColor());
+			setFillColor(dot.getFillColor());
+
+			return null;
+		}
+
+		if (merger instanceof Text) {
+			List<Line> lines = new LinkedList<Line>();
+			lines.addAll(getLines());
+			for (Line line : lines)
+				line.replaceEnd(this, merger);
+
+			removeAllLines();
+			return merger;
+		}
+
+		// if the item being merged with is a Line
+		if (merger instanceof Line) {
+			Line line = (Line) merger;
+			// if this dot is part of the line then keep it, otherwise it
+			// can be removed
+			if (line.getOppositeEnd(this) != null && getLines().contains(line))
+				return merger;
+			else
+				return null;
+		}
+
+		return merger;
+	}
+
+	/**
+	 * Sets the list of Dots (including this one) that form a closed shape.
+	 * Passing null sets this dot back to its normal (non-enclosed) state.
+	 * 
+	 * @param enclosed
+	 *            The List of Dots including this one that form a closed shape,
+	 *            or null.
+	 */
+	public void setEnclosedList(List<Dot> enclosed) {
+		_enclosure = enclosed;
+	}
+
+	/**
+	 * Returns the polygon that represents the shape created by all the Dots in
+	 * this Dot's enclosed list. If the list is null, then null is returned.
+	 * 
+	 * @return A Polygon the same shape and position as created by the Dots in
+	 *         the enclosed list.
+	 */
+	public Polygon getEnclosedShape() {
+		if (_enclosure == null)
+			return null;
+
+		Polygon poly = new Polygon();
+		for (Dot d : _enclosure) {
+			poly.addPoint(d.getX(), d.getY());
+		}
+
+		return poly;
+	}
+
+	/**
+	 * Returns the list of Dots that, along with this Dot, form an enclosed
+	 * polygon. If this Dot is not part of an enclosure null may be returned.
+	 * 
+	 * @return The List of Dots that form an enclosed shape with this Dot, or
+	 *         null if this Dot is not part of an enclosure.
+	 */
+	public List<Dot> getEnclosingDots() {
+		return _enclosure;
+	}
+
+	/**
+	 * Returns whether this Dot has an assigned enclosure list of other Dots.
+	 * The result is the same as getEnclosedShape() != null.
+	 * 
+	 * @return True if this Dot has an enclosure list of other Dots, false
+	 *         otherwise.
+	 */
+	public boolean isEnclosed() {
+		return _enclosure != null;
+	}
+
+	@Override
+	public String getTypeAndID() {
+		return "P " + getID();
+	}
+}
Index: /trunk/src/org/expeditee/items/Item.java
===================================================================
--- /trunk/src/org/expeditee/items/Item.java	(revision 6)
+++ /trunk/src/org/expeditee/items/Item.java	(revision 6)
@@ -0,0 +1,1359 @@
+package org.expeditee.items;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.geom.Area;
+import java.util.ArrayList;
+import java.util.ConcurrentModificationException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.actions.Actions;
+import org.expeditee.actions.Simple;
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.io.Conversion;
+import org.expeditee.io.Logger;
+import org.expeditee.simple.Context;
+import org.expeditee.stats.AgentStats;
+
+/**
+ * Represents everything that can be drawn on the screen (text, lines, dots,
+ * images). Each specific type is a subclass of Item.
+ * 
+ * @author jdm18
+ * 
+ */
+public abstract class Item implements Comparable<Item>, Runnable {
+
+	/**
+	 * The default Color to draw highlighting in
+	 */
+	public static final int DEFAULT_HIGHLIGHT_THICKNESS = 2;
+
+	public static final Color DEFAULT_HIGHLIGHT = Color.RED;
+
+	public static final Color DEPRESSED_HIGHLIGHT = Color.GREEN;
+
+	public static final Color LINK_COLOR = Color.BLACK;
+
+	public static final Color ACTION_COLOR = Color.BLACK;
+
+	public static final Color LINK_ACTION_COLOR = Color.RED;
+
+	public static final Color DEFAULT_FOREGROUND = Color.BLACK;
+
+	public static final Color DEFAULT_BACKGROUND = org.expeditee.gui.DisplayIO.DEFAULT_BACKGROUND;
+
+	/**
+	 * The number of pixels highlighting should extend around Items.
+	 */
+	public static final int XGRAVITY = 3;
+
+	public static final int MARGIN_RIGHT = 10;
+
+	public static final int MARGIN_LEFT = 20;
+
+	private Point _offset = new Point(0, 0);
+
+	private int _x;
+
+	private int _y;
+
+	private int _id;
+
+	private String _creationDate = null;
+
+	private boolean _linkMark = true;
+
+	private boolean _actionMark = true;
+
+	private boolean _highlight = true;
+
+	private boolean _isHighlighted = false;
+
+	// private boolean _isValidLink = true;
+
+	private Dimension _maxSize = null;
+
+	private String _owner = null;
+
+	private String _link = null;
+
+	private StringBuffer _data = new StringBuffer();
+
+	private List<String> _actionCursorEnter = null;
+
+	private List<String> _actionCursorLeave = null;
+
+	private List<String> _actionEnterFrame = null;
+
+	private List<String> _actionLeaveFrame = null;
+
+	private Color _colorFill = null;
+
+	private Color _color = null;
+
+	private Color _highlightColor = DEFAULT_HIGHLIGHT;
+
+	private Color _colorBackground = null;
+
+	private Color _colorTopShadow = null;
+
+	private Color _colorBottomShadow = null;
+
+	// the link\action circle
+	private Polygon _circle = null;
+
+	// the invalid link cross
+	private Polygon _circleCross = null;
+
+	private Frame _parent = null;
+
+	protected int _highlightThickness = 2;
+
+	// arrowhead parameters
+	private int _arrowheadLength = 0;
+
+	private double _arrowheadRatio = 0;
+
+	private Polygon _arrowhead = null;
+
+	// the list of lines that this point is part of.
+	private List<Line> _lines = new ArrayList<Line>();
+
+	private int[] _linePattern = null;
+
+	private boolean _floating = false;
+
+	// list of points constrained with this point
+	private List<Constraint> _constraints = null;
+
+	public static final Color GREEN = Color.GREEN.darker();
+
+	/**
+	 * The Colors cycled through when using function keys to set the Color of
+	 * this Item.
+	 */
+	public static final Color[] COLOR_WHEEL = { Color.BLACK, Color.RED,
+			Color.BLUE, Item.GREEN, Color.MAGENTA, Color.YELLOW.darker(),
+			DisplayIO.DEFAULT_BACKGROUND };
+
+	public static final int UNCHANGED_CURSOR = -100;
+
+	public static final int DEFAULT_CURSOR = Cursor.DEFAULT_CURSOR;
+
+	public static final int HIDDEN_CURSOR = Cursor.CUSTOM_CURSOR;
+
+	public static final int TEXT_CURSOR = Cursor.TEXT_CURSOR;
+
+	public static final int CROP_CURSOR = Cursor.CROSSHAIR_CURSOR;
+
+	public static final int JUSTIFICATION_NONE = -1;
+
+	public static final int JUSTIFICATION_FULL = 0;
+
+	public static final int JUSTIFICATION_CENTER = 1;
+
+	public static final int JUSTIFICATION_RIGHT = 2;
+
+	public static final int JUSTIFICATION_LEFT = 3;
+
+	public static final int POINTTYPE_SQUARE = -1;
+
+	public static final int POINTTYPE_CIRCLE = 0;
+
+	private LinkedList<String> _actions = null;
+
+	private String _link_frameset = null;
+
+	private String _link_template = null;
+
+	private String _fillPattern = null;
+
+	protected Item() {
+		_creationDate = Logger.EasyDateFormat("ddMMMyyyy:HHmm");
+	}
+
+	/**
+	 * Items are sorted by their Y coordinate on the screen.
+	 * 
+	 * @param i
+	 *            The Item to compare this Item to
+	 * @return a negative integer, zero, or a positive integer as this object is
+	 *         less than, equal to, or greater than the specified object.
+	 */
+	public int compareTo(Item i) {
+		return getY() - i.getY();
+	}
+
+	public static int getGravity() {
+		return org.expeditee.gui.UserSettings.Gravity;
+	}
+
+	public static boolean showLineHighlight() {
+		return org.expeditee.gui.UserSettings.LineHighlight;
+	}
+
+	public boolean equals(Object o) {
+		if (o instanceof Item) {
+			Item i = (Item) o;
+			return i.getID() == getID()
+					&& ((i.getParent() == _parent) || (i.getParent() != null && i
+							.getParent().equals(_parent)));
+
+		} else
+			return false;
+	}
+
+	/**
+	 * Adds a given line to the list of lines that this Point is an end for.
+	 * 
+	 * @param line
+	 *            The Line that this Point is an end of.
+	 */
+	public void addLine(Line line) {
+		if (_lines.contains(line)) {
+			return;
+		}
+
+		line.setColor(getColor());
+		line.setSize(getSize());
+		line.setLinePattern(getLinePattern());
+
+		_lines.add(line);
+	}
+
+	/**
+	 * Removes the given Line from the list of lines that this Dot is an end
+	 * for.
+	 * 
+	 * @param line
+	 *            The Line that this Dot is no longer an end of.
+	 */
+	public void removeLine(Line line) {
+		_lines.remove(line);
+	}
+
+	/**
+	 * Returns a list of Lines where this Dot is an end.
+	 * 
+	 * @return A list of the Lines that this Dot is an end for or null if no
+	 *         Lines have been added.
+	 */
+	public List<Line> getLines() {
+		return _lines;
+	}
+
+	public void setLines(List<Line> lines) {
+		_lines = lines;
+
+		for (Line line : lines)
+			line.setLinePattern(getLinePattern());
+	}
+
+	public void setLinePattern(int[] pattern) {
+		_linePattern = pattern;
+
+		for (Line line : getLines())
+			line.setLinePattern(pattern);
+	}
+
+	public int[] getLinePattern() {
+		return _linePattern;
+	}
+
+	/**
+	 * Clears the list of Lines that this Dot is an end of. Note: This only
+	 * clears this Dot's list and does not have any affect on the Lines or other
+	 * Dots.
+	 */
+	public void removeAllLines() {
+		_lines.clear();
+	}
+
+	/**
+	 * Returns the list of IDs of the Lines that this Dot is an end of.
+	 * 
+	 * @return The list of Line IDs that this point is part of.
+	 */
+	public String getLineIDs() {
+		String lineID = null;
+
+		if (_lines.size() > 0) {
+			lineID = "" + _lines.get(0).getID();
+
+			for (int i = 1; i < _lines.size(); i++)
+				lineID += " " + _lines.get(i).getID();
+		}
+
+		return lineID;
+	}
+
+	/**
+	 * Sets the list of lines that this point is part of (may be set to null).
+	 * 
+	 * @param lineID
+	 *            A String of line ID numbers separated by spaces.
+	 */
+	public void setLineIDs(String lineID) {
+	}
+
+	public void setFloating(boolean val) {
+		_floating = val;
+	}
+
+	public boolean isFloating() {
+		return _floating;
+	}
+
+	/**
+	 * Returns a List of any Constraints that this Dot is a memeber of.
+	 * 
+	 * @return a List of Constraints that this Dot is a member of.
+	 */
+	public List<Constraint> getConstraints() {
+		return _constraints;
+	}
+
+	public String getConstraintIDs() {
+		if (_constraints == null || _constraints.size() == 0)
+			return null;
+
+		String cons = "";
+
+		for (Constraint c : _constraints)
+			cons += c.getID() + " ";
+
+		return cons.trim();
+	}
+
+	public void setConstraintIDs(String IDs) {
+	};
+
+	/**
+	 * Adds the given Constraint to this Dot
+	 * 
+	 * @param c
+	 *            The Constraint to set this Dot as a member of.
+	 */
+	public void addConstraint(Constraint c) {
+		if (_constraints == null)
+			_constraints = new ArrayList<Constraint>();
+
+		// do not add duplicate constraint
+		if (_constraints.contains(c))
+			return;
+
+		_constraints.add(c);
+	}
+
+	public void setConstraints(List<Constraint> constraints) {
+		_constraints = constraints;
+	}
+
+	public void removeAllConstraints() {
+		if (_constraints != null)
+			_constraints.clear();
+	}
+
+	/**
+	 * Removes the given Constraint from the list of constraintss that this Dot
+	 * is a part of.
+	 * 
+	 * @param c
+	 *            The Constraint that this Dot is no longer a part of.
+	 */
+	public void removeConstraint(Constraint c) {
+		_constraints.remove(c);
+	}
+
+	/**
+	 * Returns the ID of this Item, which must be unique for the Frame.
+	 * 
+	 * @return The ID of this Item.
+	 */
+	public int getID() {
+		return _id;
+	}
+
+	/**
+	 * Sets the ID of this Item to the given Integer. Note: Items with ID's < 0
+	 * are not saved
+	 * 
+	 * @param newID
+	 *            The new ID to assign this Item.
+	 */
+	public void setID(int newID) {
+		_id = newID;
+	}
+
+	/**
+	 * Returns the Y coordinate of this Item on the screen
+	 * 
+	 * @return The Y coordinate of this Item on the screen
+	 */
+	public int getY() {
+		return _y;
+	}
+
+	/**
+	 * Returns the X coordinate of this Item on the screen
+	 * 
+	 * @return The X coordinate of this Item on the screen
+	 */
+	public int getX() {
+		return _x;
+	}
+
+	/**
+	 * Sets the position of this item on the screen
+	 * 
+	 * @param x
+	 *            The new X coordinate
+	 * @param y
+	 *            The new Y coordinate
+	 */
+	public void setPosition(int x, int y) {
+		_x = x;
+		_y = y;
+
+		updatePolygon();
+
+		// update the position of any dots that are constrained by this one
+		if (_constraints != null) {
+			for (Constraint c : _constraints) {
+				Item other = c.getOppositeEnd(this);
+
+				// only set the position if the other dot is still fixed to the
+				// frame
+				if (!other.isFloating()) {
+					if (c.getConstraintType() == Constraint.HORIZONTAL
+							&& other.getY() != getY())
+						other.setY(getY());
+
+					if (c.getConstraintType() == Constraint.VERTICAL
+							&& other.getX() != getX())
+						other.setX(getX());
+				}
+			}
+		}
+
+		for (Line line : getLines())
+			line.updatePolygon();
+	}
+
+	public void setPosition(Point position) {
+		setPosition(position.x, position.y);
+	}
+
+	public Point getPosition() {
+		return new Point(getX(), getY());
+	}
+
+	/**
+	 * Sets the position of this Item on the X axis
+	 * 
+	 * @param newX
+	 *            The position on the X axis to assign to this Item
+	 */
+	public void setX(int newX) {
+		setPosition(newX, getY());
+	}
+
+	/**
+	 * Sets the position of this Item on the Y axis
+	 * 
+	 * @param newY
+	 *            The position on the Y axis to assign to this Item
+	 */
+	public void setY(int newY) {
+		setPosition(getX(), newY);
+	}
+
+	/**
+	 * Returns the name of a Frame that this Item links to, or null if this Item
+	 * has no link.
+	 * 
+	 * @return The name of a Frame that this Item links to (if any) or null if
+	 *         this Item does not link to anything.
+	 */
+	public String getLink() {
+		return _link;
+	}
+
+	public String getAbsoluteLink() {
+		if (_link == null)
+			return null;
+		assert (_parent!= null);
+		if (_parent == null)
+			return null;
+
+		// if its a relative link then return absolute
+		if (FrameIO.isPositiveInteger(_link)) {
+			return _parent.getFramesetNameAdjusted()
+					+ Conversion.getFrameNumber(_link);
+		}
+		return _link;
+	}
+
+	/**
+	 * Links this item to the given Frame, this may be set to null to remove a
+	 * link.
+	 * 
+	 * @param frameName
+	 *            The name of the Frame to link this item to.
+	 */
+	public void setLink(String frameName) {
+		_link = frameName;
+	}
+
+	/*
+	 * public void setLinkValid(boolean val) { _isValidLink = val; }
+	 */
+
+	/**
+	 * Tests if the item link is a valid framename, that is, the String must
+	 * begin with a character, end with a number with 0 or more letters and
+	 * numbers in between. If there is a dot in the framename all the chars
+	 * after it must be digits.
+	 * 
+	 * @return True if the given framename is proper, false otherwise.
+	 */
+	public boolean isLinkValid() {
+		if (FrameIO.isPositiveInteger(getLink()))
+			return true;
+
+		if (FrameIO.isValidFrameName(getLink()))
+			return true;
+		return false;
+	}
+
+	public void setLinkFrameset(String frameset) {
+		_link_frameset = frameset;
+	}
+
+	public String getLinkFrameset() {
+		return _link_frameset;
+	}
+
+	public void setLinkTemplate(String template) {
+		_link_template = template;
+	}
+
+	public String getLinkTemplate() {
+		return _link_template;
+	}
+
+	/**
+	 * Adds an action to this Item.
+	 * 
+	 * @param action
+	 *            The KMS action language to add to this Item
+	 */
+	public void addAction(String action) {
+		if (action == null || action.equals("")) {
+			return;
+		}
+
+		if (_actions == null)
+			_actions = new LinkedList<String>();
+		_actions.add(action);
+	}
+
+	/**
+	 * Sets any action code (KMS action language) that should be associated with
+	 * this Item Each entry in the list is one line of code
+	 * 
+	 * @param actions
+	 *            The lines of code to associate with this Item
+	 */
+	public void setAction(List<String> actions) {
+		if (actions == null || actions.size() == 0)
+			_actions = null;
+		else
+			_actions = new LinkedList<String>(actions);
+	}
+
+	/**
+	 * Returns a list of any action code (KMS action language) that is currently
+	 * associated with this Item
+	 * 
+	 * @return A List of action code associated with this Item, or null if none
+	 *         has been assigned.
+	 */
+	public List<String> getAction() {
+		return _actions;
+	}
+
+	public String getFirstAction() {
+		if (_actions == null)
+			return null;
+		return _actions.getFirst();
+	}
+
+	/**
+	 * Displays this item directly on the screen. Note: All Items are
+	 * responsible for their own drawing, buffering, etc.
+	 * 
+	 * @param g
+	 *            The Graphics to draw this Item on.
+	 */
+	public abstract void paint(Graphics2D g);
+
+	/**
+	 * Every Item has an area around it defined by a Shape (typically a
+	 * rectangle), this method returns true if the given x,y pair lies within
+	 * the area and false otherwise.
+	 * 
+	 * @param x
+	 *            The x coordinate to check
+	 * @param y
+	 *            The y coordinate to check
+	 * @return True if the Shape around this Item contains the given x,y pair,
+	 *         false otherwise.
+	 */
+	public boolean contains(int x, int y) {
+		return getPolygon().contains(x, y);
+	}
+
+	/**
+	 * Returns the Shape that surrounds this Item representing this Item's
+	 * 'gravity'.
+	 * 
+	 * @return The Shape (rectangle) surrounding this Item, which represents
+	 *         this Items 'gravity'.
+	 */
+	public abstract Polygon getPolygon();
+
+	public Area getArea() {
+		return new Area(getPolygon());
+	}
+
+	protected abstract void updatePolygon();
+
+	/**
+	 * Returns the height (in pixels) of this Item's surrounding area.
+	 * 
+	 * @return The height (in pixels) of this Item's surrounding area as
+	 *         returned by getArea().
+	 */
+	public int getBoundsHeight() {
+		return getPolygon().getBounds().height;
+	}
+
+	/**
+	 * Returns the width (in pixels) of this Item's surrounding area.
+	 * 
+	 * @return The width (in pixels) of this Item's surrounding area as returned
+	 *         by getArea().
+	 */
+	public int getBoundsWidth() {
+		return getPolygon().getBounds().width;
+	}
+
+	/**
+	 * Checks if the given Shape intersects with the Shape around this Item.
+	 * Note: Both Shape objects should be rectangles for this to work properly.
+	 * 
+	 * @param s
+	 *            The Shape to check.
+	 * @return True if the two Shapes overlap, False otherwise.
+	 */
+	public boolean intersects(Polygon p) {
+		if (p == null)
+			return false;
+
+		// return p.getBounds().intersects(getArea().getBounds());
+		Area a = new Area(p);
+		a.intersect(this.getArea());
+		return !a.isEmpty();
+	}
+
+	/**
+	 * Paints any highlighting of this Item. This may include changing the
+	 * thickness (lines) or painting a box around the item (Text, Images). If
+	 * val is True then the Graphics Color is changed to the highlight Color, if
+	 * False then the Graphics Color is left unchanged (for clearing of
+	 * highlighting).
+	 * 
+	 * @param val
+	 *            True if this Item should be highlighted, false if the
+	 *            highlighting is being cleared.\
+	 * @return The desired mouse cursor when this Item is highlighted (negative
+	 *         means no change)
+	 */
+	public int showHighlight(boolean val) {
+		_highlightThickness = DEFAULT_HIGHLIGHT_THICKNESS;
+		return showHighlight(val, DEFAULT_HIGHLIGHT);
+	}
+
+	public int showDepressedHighlight(boolean val) {
+		_highlightThickness = DEFAULT_HIGHLIGHT_THICKNESS;
+		return showHighlight(val, DEPRESSED_HIGHLIGHT);
+	}
+
+	public int showHighlight(boolean val, Color c) {
+		_isHighlighted = val;
+		if (c != null)
+			_highlightColor = c;
+		else
+			_highlightColor = DEFAULT_HIGHLIGHT;
+
+		return Item.UNCHANGED_CURSOR;
+	}
+
+	/**
+	 * Returns True if this Item is currently highlighted.
+	 * 
+	 * @return True if this Item is currently highlighted on the screen, False
+	 *         otherwise.
+	 */
+	public boolean isHighlighted() {
+		return _isHighlighted;
+	}
+
+	public Color getHighlightColor() {
+		return _highlightColor;
+	}
+
+	/**
+	 * Returns a deep copy of this Item, note: it is up to the receiver to
+	 * change the Item ID etc as necessary.
+	 * 
+	 * @return A deep copy of this Item.
+	 */
+	public abstract Item copy();
+
+	public static void DuplicateItem(Item source, Item dest) {
+		dest.setX(source.getX());
+		dest.setY(source.getY());
+
+		dest.setAction(source.getAction());
+		dest.setActionCursorEnter(source.getActionCursorEnter());
+		dest.setActionCursorLeave(source.getActionCursorLeave());
+		dest.setActionEnterFrame(source.getActionEnterFrame());
+		dest.setActionLeaveFrame(source.getActionLeaveFrame());
+		dest.setActionMark(source.getActionMark());
+		dest.setAnnotation(source.isAnnotation());
+
+		dest.setBackgroundColor(source.getBackgroundColor());
+		dest.setBottomShadowColor(source.getBottomShadowColor());
+		dest.setColor(source.getColor());
+
+		dest.setData(source.getData());
+		dest.setFillColor(source.getFillColor());
+		dest.setFillPattern(source.getFillPattern());
+
+		dest.setHighlight(source.getHighlight());
+		dest.setLink(source.getLink());
+		dest.setLinkFrameset(source.getLinkFrameset());
+		dest.setLinkMark(source.getLinkMark());
+		dest.setLinkTemplate(source.getLinkTemplate());
+
+		dest.setMaxSize(source.getMaxSize());
+		dest.setOffset(source.getOffset());
+		dest.setOwner(source.getOwner());
+		dest.setSize(source.getSize());
+		dest.setTopShadowColor(source.getTopShadowColor());
+		dest.setLinePattern(source.getLinePattern());
+
+		dest.setFloating(source.isFloating());
+		dest.setArrow(source.getArrowheadLength(), source.getArrowheadRatio());
+
+		dest.setParent(source.getParent());
+		// Add the copied item to the frame
+		if (source.getParent() != null) {
+			dest.setID(source.getParent().getNextItemID());
+		} else {
+			dest.setID(source.getID());
+		}
+	}
+
+	/**
+	 * Merges this Item with the given Item. The merger Item should be left
+	 * unchanged after this method. The merger may or may not be the same class
+	 * as this Item, exact behaviour depends on the subclass, No-op is allowed.
+	 * 
+	 * @param merger
+	 *            The Item to merge with
+	 * @param mouseX
+	 *            The X coordinate of the mouse when performing this merge
+	 *            operation
+	 * @param mouseY
+	 *            The Y coordinate of the mouse when performing this merge
+	 *            operation
+	 * @return any Item that should remain on the cursor
+	 */
+	public abstract Item merge(Item merger, int mouseX, int mouseY);
+
+	/**
+	 * Sets the maximum coordinates on the screen that this item may occupy.
+	 * This is used by Text items to compute word-wrapping lengths.
+	 * 
+	 * @param d
+	 *            The Maximum size of the Frame containing this Item.
+	 */
+	public void setMaxSize(Dimension d) {
+		if (d != null) {
+			_maxSize = d;
+			updatePolygon();
+		}
+	}
+
+	public Dimension getMaxSize() {
+		return _maxSize;
+	}
+
+	public Color getColor() {
+		return _color;
+	}
+
+	/**
+	 * Returns the foreground Color of this Item.
+	 * 
+	 * @return The Color of this item (foreground)
+	 */
+	public Color getPaintColor() {
+		if (_color == null) {
+			if (getParent() != null)
+				return getParent().getPaintForegroundColor();
+
+			if (DisplayIO.getCurrentFrame() == null)
+				return DEFAULT_FOREGROUND;
+
+			return DisplayIO.getCurrentFrame().getPaintForegroundColor();
+		}
+
+		return _color;
+	}
+
+	protected Color getPaintHighlightColor() {
+		if (getParent() != null
+				&& getParent().getPaintBackgroundColor()
+						.equals(_highlightColor))
+			return getParent().getPaintForegroundColor();
+
+		return _highlightColor;
+	}
+
+	/**
+	 * Sets the foreground Color of this Item to the given Color.
+	 * 
+	 * @param c
+	 */
+	public void setColor(Color c) {
+		_color = c;
+
+		// update the colour of any lines
+		for (Line line : getLines())
+			line.setColor(c);
+	}
+
+	/**
+	 * Returns the Color being used to shade the top half of this Item's border.
+	 * This can be NULL if no Color is being used
+	 * 
+	 * @return The Color displayed on the top\left half of this Item's border.
+	 */
+	public Color getTopShadowColor() {
+		return _colorTopShadow;
+	}
+
+	/**
+	 * Sets the Color to use on the top and left sections of this Item's border.
+	 * If top is NULL, then the Item's background Color will be used.
+	 * 
+	 * @param top
+	 *            The Color to display in the top and left sections of this
+	 *            Item's border.
+	 */
+	public void setTopShadowColor(Color top) {
+		_colorTopShadow = top;
+	}
+
+	/**
+	 * Returns the Color being used to shade the bottom half of this Item's
+	 * border. This can be NULL if no Color is being used
+	 * 
+	 * @return The Color displayed on the bottom\right half of this Item's
+	 *         border.
+	 */
+	public Color getBottomShadowColor() {
+		return _colorBottomShadow;
+	}
+
+	/**
+	 * Sets the Color to use on the bottom and right sections of this Item's
+	 * border. If top is NULL, then the Item's background Color will be used.
+	 * 
+	 * @param top
+	 *            The Color to display in the bottom and right sections of this
+	 *            Item's border.
+	 */
+	public void setBottomShadowColor(Color bottom) {
+		_colorBottomShadow = bottom;
+	}
+
+	protected Polygon getCircle() {
+		if (_circle == null) {
+			int points = 16;
+
+			double radians = 0.0;
+			int xPoints[] = new int[points];
+			int yPoints[] = new int[xPoints.length];
+
+			for (int i = 0; i < xPoints.length; i++) {
+				// circle looks best if these values are not related to gravity
+				xPoints[i] = (int) (3.5 * Math.cos(radians)) + 6;// (2 *
+				// GRAVITY);
+				yPoints[i] = (int) (3.5 * Math.sin(radians)) + 3;// GRAVITY;
+				radians += (2.0 * Math.PI) / xPoints.length;
+			}
+
+			_circle = new Polygon(xPoints, yPoints, xPoints.length);
+		}
+
+		return _circle;
+	}
+
+	protected Polygon getCircleCross() {
+
+		if (_circleCross == null) {
+			_circleCross = new Polygon();
+
+			Rectangle bounds = getCircle().getBounds();
+			int x1 = (int) bounds.getMinX();
+			int x2 = (int) bounds.getMaxX();
+			int y1 = (int) bounds.getMinY();
+			int y2 = (int) bounds.getMaxY();
+			int midX = ((x2 - x1) / 2) + x1;
+			int midY = ((y2 - y1) / 2) + y1;
+
+			_circleCross.addPoint(x1, y1);
+			_circleCross.addPoint(x2, y2);
+			_circleCross.addPoint(midX, midY);
+			_circleCross.addPoint(x1, y2);
+			_circleCross.addPoint(x2, y1);
+			_circleCross.addPoint(midX, midY);
+		}
+
+		return _circleCross;
+	}
+
+	public void setFillColor(Color c) {
+		_colorFill = c;
+
+		for (Line line : _lines) {
+			Item other = line.getOppositeEnd(this);
+			if (other.getFillColor() != c)
+				other.setFillColor(c);
+		}
+	}
+
+	public void setBackgroundColor(Color c) {
+		_colorBackground = c;
+	}
+
+	public Color getBackgroundColor() {
+		return _colorBackground;
+	}
+
+	public Color getPaintBackgroundColor() {
+		if (_colorBackground == null) {
+			if (getParent() != null && getParent().getBackgroundColor() != null)
+				return getParent().getBackgroundColor();
+
+			return DEFAULT_BACKGROUND;
+		}
+
+		return _colorBackground;
+	}
+
+	public Color getFillColor() {
+		return _colorFill;
+	}
+
+	/**
+	 * Returns the size of this Item. For Text this is the Font size, for Lines
+	 * and Dots this is the thickness.
+	 * 
+	 * @return The size of this Item.
+	 */
+	public abstract int getSize();
+
+	/**
+	 * Sets the size of this Item. For Text this is the Font size. For Lines and
+	 * Dots this is the thickness.
+	 */
+	public abstract void setSize(int size);
+
+	/**
+	 * Sets whether this Item is an Annotation.
+	 * 
+	 * @param val
+	 *            True if this Item is an Annotation, False otherwise.
+	 */
+	public abstract void setAnnotation(boolean val);
+
+	/**
+	 * Note: Pictures always return False, as they should be drawn even when no
+	 * other annotation Items are.
+	 * 
+	 * @return True if this Item is an annotation, False otherwise.
+	 */
+	public abstract boolean isAnnotation();
+
+	public List<Item> getConnected() {
+		List<Item> conn = new LinkedList<Item>();
+		conn.add(this);
+
+		conn.addAll(getLines());
+		return conn;
+	}
+
+	public List<Item> getAllConnected() {
+		List<Item> list = new LinkedList<Item>();
+		addAllConnected(list);
+		return list;
+	}
+
+	public void addAllConnected(List<Item> connected) {
+		if (!connected.contains(this))
+			connected.add(this);
+
+		for (Line line : getLines()) {
+			if (!connected.contains(line))
+				line.addAllConnected(connected);
+		}
+	}
+
+	public void setOffset(Point p) {
+		_offset.setLocation(p);
+	}
+
+	public void setOffset(int x, int y) {
+		_offset.setLocation(x, y);
+	}
+
+	public Point getOffset() {
+		return _offset;
+	}
+
+	public void setParent(Frame frame) {
+		_parent = frame;
+	}
+
+	public Frame getParent() {
+		return _parent;
+	}
+
+	public void setActionMark(boolean val) {
+		_actionMark = val;
+	}
+
+	public boolean getActionMark() {
+		return _actionMark;
+	}
+
+	public void setLinkMark(boolean val) {
+		_linkMark = val;
+	}
+
+	public boolean getLinkMark() {
+		return _linkMark;
+	}
+
+	public void setOwner(String own) {
+		_owner = own;
+	}
+
+	public String getOwner() {
+		return _owner;
+	}
+
+	public void setHighlight(boolean val) {
+		_highlight = val;
+	}
+
+	public boolean getHighlight() {
+		return _highlight;
+	}
+
+	public String getData() {
+		if (_data != null && _data.length() > 0)
+			return _data.toString();
+		return null;
+	}
+
+	public void setData(String newData) {
+		if (newData != null)
+			_data = new StringBuffer(newData);
+		else
+			_data = null;
+	}
+
+	public List<String> getActionCursorEnter() {
+		return _actionCursorEnter;
+	}
+
+	public void setActionCursorEnter(List<String> enter) {
+		_actionCursorEnter = enter;
+	}
+
+	public List<String> getActionCursorLeave() {
+		return _actionCursorLeave;
+	}
+
+	public void setActionCursorLeave(List<String> leave) {
+		_actionCursorLeave = leave;
+	}
+
+	public List<String> getActionEnterFrame() {
+		return _actionEnterFrame;
+	}
+
+	public void setActionEnterFrame(List<String> enter) {
+		_actionEnterFrame = enter;
+	}
+
+	public List<String> getActionLeaveFrame() {
+		return _actionLeaveFrame;
+	}
+
+	public void setActionLeaveFrame(List<String> leave) {
+		_actionLeaveFrame = leave;
+	}
+
+	public void setFillPattern(String patternLink) {
+		_fillPattern = patternLink;
+	}
+
+	public String getFillPattern() {
+		return _fillPattern;
+	}
+
+	/**
+	 * Used to set this Line as an Arrow. If length and ratio are 0, no arrow is
+	 * shown.
+	 * 
+	 * @param length
+	 *            The how far down the shaft of the line the arrowhead should
+	 *            come.
+	 * @param ratio
+	 *            The ratio of the arrow's length to its width.
+	 */
+	public void setArrow(int length, double ratio) {
+		_arrowheadLength = length;
+		_arrowheadRatio = ratio;
+		updateArrowPolygon();
+	}
+
+	public int getArrowheadLength() {
+		return _arrowheadLength;
+	}
+
+	public double getArrowheadRatio() {
+		return _arrowheadRatio;
+	}
+
+	public void setArrowheadLength(int length) {
+		_arrowheadLength = length;
+		updateArrowPolygon();
+	}
+
+	public void setArrowheadRatio(double ratio) {
+		_arrowheadRatio = ratio;
+		updateArrowPolygon();
+	}
+
+	private void updateArrowPolygon() {
+		if (getArrowheadLength() < 0 || getArrowheadRatio() < 0)
+			_arrowhead = null;
+		else {
+			_arrowhead = new Polygon();
+			_arrowhead.addPoint((int) getX(), (int) getY());
+			_arrowhead
+					.addPoint(
+							(int) getX() - getArrowheadLength(),
+							(int) (getY() - (getArrowheadLength() * getArrowheadRatio())));
+			_arrowhead.addPoint((int) getX(), (int) getY());
+			_arrowhead
+					.addPoint(
+							(int) getX() - getArrowheadLength(),
+							(int) (getY() + (getArrowheadLength() * getArrowheadRatio())));
+		}
+	}
+
+	protected boolean hasVisibleArrow() {
+		return false;
+	}
+
+	public String getArrow() {
+		if (!hasVisibleArrow())
+			return null;
+
+		String ratio = "" + getArrowheadRatio();
+		if (ratio.length() - ratio.indexOf(".") > 2)
+			ratio = ratio.substring(0, ratio.indexOf(".") + 3);
+
+		return getArrowheadLength() + " " + ratio;
+	}
+
+	public Polygon getArrowhead() {
+		return _arrowhead;
+	}
+
+	public void setArrowhead(Polygon arrow) {
+		_arrowhead = arrow;
+	}
+
+	/**
+	 * This method performs all the actions in an items list. If it contains a
+	 * link as well the link is used as the source frame for all acitons.
+	 */
+	public void performActions() {
+		Frame sourceFrame = null;
+		// if a link exists make it the source frame for this action
+		if (getLink() != null) {
+			sourceFrame = FrameUtils.getFrame(getLink());
+		}
+		// if no link exists or the link is bad then use the
+		// currently displayed frame as the source frame for the
+		// action
+		if (sourceFrame == null) {
+			sourceFrame = DisplayIO.getCurrentFrame();
+		}
+
+		for (String s : getAction()) {
+			Actions.PerformAction(sourceFrame, this, s);
+		}
+	}
+
+	/**
+	 * Checks if this item is a frame title.
+	 * 
+	 * @return true if the item is a frame title
+	 */
+	/*
+	 * public boolean isTitle() { // check if the title has been assigned if
+	 * (getID() >= 0 && this instanceof Text) if (getX() < 200 && getY() <
+	 * getSize() + system.io.KMSConversion.Y_ADJUST) return true; return false; }
+	 */
+
+	public boolean isOldTag() {
+		if (this instanceof Text)
+			if (((Text) this).getText().get(0).toLowerCase().equals("@old"))
+				return true;
+		return false;
+	}
+
+	public boolean isFrameName() {
+		if (this.getParent() == null || this.getParent().getName() != this)
+			return false;
+		return true;
+	}
+
+	public boolean isFrameTitle() {
+		if (this.getParent() == null || this.getParent().getTitle() != this)
+			return false;
+		return true;
+	}
+
+	/**
+	 * Sets the created date of this Frame to the given String.
+	 * 
+	 * @param date
+	 *            The date to use for this Frame.
+	 */
+	public void setDateCreated(String date) {
+		_creationDate = date;
+	}
+
+	public String getDateCreated() {
+		return _creationDate;
+	}
+
+	public float getThickness() {
+		return 0;
+	}
+
+	public void setThickness(float thick) throws UnsupportedOperationException {
+		throw new UnsupportedOperationException(
+				"Item type does not support thickness attribute!");
+	}
+
+	public void setWidth(int width) throws UnsupportedOperationException {
+		throw new UnsupportedOperationException(
+				"Item type does not support width attribute!");
+	}
+
+	public int getWidth() {
+		return 0;
+	}
+
+	public void run() {
+		try {
+			AgentStats.reset();
+			FrameGraphics
+					.DisplayMessage("Running SimpleProgram...", Color.BLUE);
+			Simple.RunFrameAndReportError(this, new Context());
+			Simple.ProgramFinished();
+			FrameGraphics.DisplayMessage(AgentStats.getStats(), GREEN);
+		} catch (ConcurrentModificationException ce) {
+			ce.printStackTrace();
+		} catch (Exception e) {
+			FrameGraphics.LinkedErrorMessage(e.getMessage());
+			Simple.ProgramFinished();
+		}
+	}
+
+	public String getTypeAndID() {
+		return "T " + getID();
+	}
+
+	/**
+	 * Check if it has a relative link if so make it absolute.
+	 * 
+	 */
+	public void setAbsoluteLink() {
+		if (_link == null)
+			return;
+		// Check if all the characters are digits and hence it is a relative
+		// link
+		for (int i = 0; i < _link.length(); i++) {
+			if (!Character.isDigit(_link.charAt(i)))
+				return;
+		}
+
+		// Make it an absolute link
+		String framesetName;
+
+		if (_parent == null)
+			framesetName = DisplayIO.getCurrentFrame()
+					.getFramesetNameAdjusted();
+		else
+			framesetName = _parent.getFramesetNameAdjusted();
+
+		_link = framesetName + _link;
+	}
+
+	public void setRelativeLink() {
+		if (_link == null)
+			return;
+		assert (_parent != null);
+		// Check if the link is for the current frameset
+		if (_parent.getFramesetName().equalsIgnoreCase(
+				Conversion.getFrameset(_link))) {
+			_link = "" + Conversion.getFrameNumber(_link);
+		}
+	}
+}
Index: /trunk/src/org/expeditee/items/ItemPermission.java
===================================================================
--- /trunk/src/org/expeditee/items/ItemPermission.java	(revision 6)
+++ /trunk/src/org/expeditee/items/ItemPermission.java	(revision 6)
@@ -0,0 +1,23 @@
+package org.expeditee.items;
+
+public class ItemPermission {
+
+	public static final int PERMISSION_NONE = 0;
+
+	public static final int PERMISSION_FOLLOW_LINKS = 1;
+
+	public static final int PERMISSION_COPY = 2;
+
+	public static final int PERMISSION_TDFC = 3;
+
+	public static final int PERMISSION_FULL = 4;
+
+	public Item Item;
+
+	public int Permission;
+
+	public ItemPermission(Item item, int perm) {
+		Item = item;
+		Permission = perm;
+	}
+}
Index: /trunk/src/org/expeditee/items/ItemUtils.java
===================================================================
--- /trunk/src/org/expeditee/items/ItemUtils.java	(revision 6)
+++ /trunk/src/org/expeditee/items/ItemUtils.java	(revision 6)
@@ -0,0 +1,684 @@
+package org.expeditee.items;
+
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Image;
+import java.awt.image.ImageObserver;
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JFrame;
+
+import org.expeditee.gui.DisplayIO;
+
+//Static methods that provide functions for the objects\
+//mostly to transform values (string -> color etc).
+
+/**
+ * Static methods that provide functions for use in Items.
+ */
+public class ItemUtils {
+	private static JFrame _jf = new JFrame();
+
+	// Tag constants
+	public static final int TAG_SORT = 0;
+
+	public static final int TAG_JOIN = 1;
+
+	public static final int TAG_INDENT = 2;
+
+	public static final int TAG_OVERLAY = 3;
+
+	public static final int TAG_ACTIVE_OVERLAY = 4;
+
+	public static final int TAG_IMAGE = 5;
+
+	public static final int TAG_ITEM_TEMPLATE = 6;
+
+	public static final int TAG_ANNOTATION_TEMPLATE = 7;
+
+	public static final int TAG_CODE_COMMENT_TEMPLATE = 8;
+
+	public static final int TAG_MENU = 9;
+
+	public static final int TAG_MENU_NEXT = 10;
+
+	public static final int TAG_PARENT = 11;
+
+	public static final int TAG_LITERAL = 12;
+
+	public static final int TAG_FRAME_IMAGE = 13;
+
+	public static final int TAG_BACKUP = 14;
+
+	public static final int TAG_POINTTYPE = 15;
+
+	public static final int TAG_MIN = 0;
+
+	public static final int TAG_MAX = 15;
+
+	/**
+	 * Determines if the given List of Items contains an Item that is one of the
+	 * pre-defined tags.
+	 * 
+	 * @param items
+	 *            The list of Items to search through
+	 * @param tag
+	 *            The Tag to search for, this should correspond to one of the
+	 *            predefined constants in this class
+	 * @return True if an Item was found that is the given Tag, False otherwise.
+	 */
+	public static boolean ContainsTag(List<Item> items, int tag) {
+		return ContainsTag(items, GetTag(tag));
+	}
+
+	public static boolean ContainsTag(List<Item> items, String tag) {
+		return (FindTag(items, tag) != null);
+	}
+
+	/**
+	 * Searches the given List of Items for an Item that is one of the
+	 * pre-defined tags.
+	 * 
+	 * @param items
+	 *            The list of Items to search through
+	 * @param tag
+	 *            The Tag to search for, this should correspond to one of the
+	 *            predefined constants in this class
+	 * @return The Item that is the given tag if one is found, or False if none
+	 *         is found
+	 */
+	public static Text FindTag(List<Item> items, int tag) {
+		return FindTag(items, GetTag(tag));
+	}
+
+	/**
+	 * Searches the given List of Items for an Item that is the given tag
+	 * 
+	 * @param items
+	 *            The list of Items to search through
+	 * @param toFind
+	 *            The Tag to search for, this should include the at (@) symbol
+	 * @return The Item that is the given tag if one is found, or False if none
+	 *         is found
+	 */
+	public static Text FindTag(List<Item> items, String toFind) {
+		for (Item i : items) {
+			if (i instanceof Text && i.isAnnotation())
+				if (((Text) i).startsWith(toFind))
+					return (Text) i;
+		}
+
+		return null;
+	}
+
+	/**
+	 * Determines if the given Item is one of the pre-defined tags in this class
+	 * 
+	 * @param toCheck
+	 *            The Item to check
+	 * @param tag
+	 *            The tag to check the Item against, this should correspond to
+	 *            one of the constants defined in this class
+	 * @return True if the Item matches the given tag, false otherwise
+	 */
+	public static boolean isTag(Item toCheck, int tag) {
+		/*
+		 * if( !(toCheck instanceof Text) || !toCheck.isAnnotation()) return
+		 * false;
+		 * 
+		 * Text txt = (Text) toCheck; //tags are ase-insensitive
+		 * if(String.CASE_INSENSITIVE_ORDER.compare(txt.getFirstLine(),GetTag(tag)) ==
+		 * 0 || txt.startsWith(GetTag(tag) + " ")) return true;
+		 * 
+		 * return false;
+		 */
+		return isTag(toCheck, GetTag(tag));
+	}
+
+	/**
+	 * Checks if the given Item contains the desired tag (case insensitive)
+	 * 
+	 * @param toCheck
+	 *            The Item to check for the given tag
+	 * @param tag
+	 *            The tag to check for in the given Item
+	 * @return True if the tag is found in the given Item, False otherwise.
+	 */
+	public static boolean isTag(Item toCheck, String tag) {
+		if (!(toCheck instanceof Text) || !toCheck.isAnnotation())
+			return false;
+
+		Text txt = (Text) toCheck;
+		// tags are ase-insensitive
+		if (String.CASE_INSENSITIVE_ORDER.compare(txt.getFirstLine(), tag) == 0
+				|| txt.startsWith(tag + " "))
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * Strips off the given tag from the given String, and returns wathever is
+	 * left
+	 * 
+	 * @param toStrip
+	 *            The String to strip the Tag from
+	 * @param tag
+	 *            The tag to remove from the String
+	 * @return The String that results from removing the given Tag from the
+	 *         given String, or null if the given String is not the given Tag
+	 */
+	public static String StripTag(String toStrip, String tag) {
+		if (toStrip.toLowerCase().startsWith(tag.toLowerCase())) {
+			toStrip = toStrip.substring(tag.length()).trim();
+			return toStrip;
+		}
+
+		/**
+		 * TODO: Change this to use REGEX
+		 */
+
+		return null;
+	}
+
+	/**
+	 * The same as StripTag(String, String), but this method iterates through
+	 * the list of pre-defined tags until one is found.
+	 * 
+	 * @param toStrip
+	 *            The String to strip the tag from
+	 * @return The String that results from stripping off the Tag, or null if
+	 *         the given String was not a tag
+	 */
+	public static String StripTag(String toStrip) {
+		// there must be something left after stripping
+		if (toStrip == null)// || toStrip.trim().indexOf(" ") < 0)
+			return toStrip;
+
+		for (int i = TAG_MIN; i <= TAG_MAX; i++) {
+			String res = StripTag(toStrip, GetTag(i));
+			if (res != null)
+				return res;
+		}
+
+		return null;
+	}
+
+	/**
+	 * Strips the first character from a string if it is the @ symbol
+	 * @param toStrip
+	 *            the string to be stripped
+	 * @return the stripped version of the string
+	 */
+	public static String StripTagSymbol(String toStrip) {
+		// there must be something left after stripping
+		if (toStrip != null) {
+			if (toStrip.length() > 0) {
+				if (toStrip.charAt(0) == '@') {
+					return toStrip.substring(1);
+				}
+			}
+		}
+
+		return toStrip;
+	}
+
+	/**
+	 * Converts the given int to the String tag. The int should correspond to
+	 * one of the constants in this class, if it does not this method will
+	 * return null.
+	 * 
+	 * @param tag
+	 *            The int corresponding to the constants in this class of which
+	 *            tag to return
+	 * @return The String representation of the given Tag, or null if the given
+	 *         value does not have a tag associated
+	 */
+	public static String GetTag(int tag) {
+		switch (tag) {
+		case TAG_SORT:
+			return "@sort";
+		case TAG_JOIN:
+			return "@join";
+		case TAG_INDENT:
+			return "@indent";
+		case TAG_OVERLAY:
+			return "@o";
+		case TAG_ACTIVE_OVERLAY:
+			return "@ao";
+		case TAG_IMAGE:
+			return "@i:";
+		case TAG_ITEM_TEMPLATE:
+			return "@itemtemplate";
+		case TAG_ANNOTATION_TEMPLATE:
+			return "@annotationtemplate";
+		case TAG_CODE_COMMENT_TEMPLATE:
+			return "@commenttemplate";
+		case TAG_MENU:
+			return "@menu";
+		case TAG_MENU_NEXT:
+			return "@nextmenu";
+		case TAG_PARENT:
+			return "@parent";
+		case TAG_LITERAL:
+			return "@lit";
+		case TAG_FRAME_IMAGE:
+			return "@f";
+		case TAG_BACKUP:
+			return "@old";
+		case TAG_POINTTYPE:
+			return "@pointtype:";
+		default:
+			return null;
+		}
+	}
+
+	/**
+	 * Returns a FontMetrics object that corresponds to the given Font.
+	 * 
+	 * @param font
+	 *            The Font to derive the FontMetrics from
+	 * @return FontMetrics that correspond to the given Font
+	 */
+	public static FontMetrics getFontMetrics(Font font) {
+		return _jf.getFontMetrics(font);
+	}
+
+	public static Image CreateImage(int x, int y) {
+		return _jf.createImage(x, y);
+	}
+
+	/**
+	 * Creates a picture object from the information stored in the given Text
+	 * object. <br>
+	 * The paths searched are in the following order:<br>
+	 * /images/<br>
+	 * the source text as a relative path (from program root folder). <br>
+	 * the source text as an absolute path <br>
+	 * <br>
+	 * If the Image file cannot be found on disk null is returned.
+	 * 
+	 * @param source
+	 *            The Text file containing the Picture infomation
+	 * @return The Picture object representing the file, or Null if the file is
+	 *         not found.
+	 */
+	public static Picture CreatePicture(Text source, ImageObserver observer) {
+		String text = source.getTextNoList();
+		String path = "";
+		String size = "";
+
+		try {
+			// remove @i tag
+			text = text.replaceFirst("@i:", "");
+			text = text.replaceAll("\n", "");
+			text = text.trim();
+
+			if (text.indexOf(".") >= text.lastIndexOf(" "))
+				path = text;
+			else
+				path = text.substring(0, text.lastIndexOf(" "));
+
+			size = text.substring(path.length()).trim();
+
+			// try images subdirectory
+			File file = null;
+
+			for (String dir : org.expeditee.gui.UserSettings.ImageDirs) {
+				file = new File(dir + path);
+				if (file.exists() && !file.isDirectory())
+					break;
+			}
+
+			if (file == null || !file.exists() || file.isDirectory())
+				file = new File(path);
+
+			// try relative path
+			if (!file.exists() || file.isDirectory()) {
+				URL picture = new Object().getClass().getResource(path);
+
+				// decode to remove %20 in windows folder names
+				if (picture != null) {
+					try {
+						path = URLDecoder.decode(picture.getFile(), "UTF-8");
+					} catch (UnsupportedEncodingException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+				}
+
+			} else
+				path = file.getPath();
+
+			// if the image isn't found by now, give up.
+			file = new File(path);
+			if (!file.exists() || file.isDirectory()) {
+				return null;
+			}
+
+		} catch (Exception e) {
+			return null;
+		}
+
+		try {
+			Picture pic = new Picture(source, path, size, observer);
+
+			return pic;
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+
+	}
+
+	public static Picture CreateFramePicture(Text source, ImageObserver observer) {
+		String size = source.getFirstLine();
+
+		// remove @f tag
+		size = size.replaceFirst("@f", "");
+		size = size.trim();
+
+		try {
+			Picture pic = new Picture(source, null, size, observer);
+			return pic;
+		} catch (Exception e) {
+			//e.printStackTrace();
+			return null;
+		}
+	}
+
+	/**
+	 * Creates a deep copy of the given List of Items.
+	 * 
+	 * @param toCopy
+	 *            The list of Items to copy
+	 * @return A list containing a copy of all Items in the given List
+	 */
+	public static List<Item> CopyItems(List<Item> toCopy) {
+		// The copies to return
+		List<Item> copies = new ArrayList<Item>();
+
+		// list of dots at the end of lines
+		List<Item> lineEnds = new ArrayList<Item>();
+		List<Line> lines = new ArrayList<Line>();
+
+		List<Item> vConst = new ArrayList<Item>();
+		List<Item> hConst = new ArrayList<Item>();
+
+		List<Item> seen = new ArrayList<Item>();
+
+		for (Item i : toCopy) {
+			// lines are recreated later
+			if (i instanceof Line) {
+				Line line = (Line) i;
+				lines.add(line);
+
+				lineEnds.add(line.getEndItem());
+				lineEnds.add(line.getStartItem());
+
+				List<Constraint> consts = line.getStartItem().getConstraints();
+				if (consts != null) {
+					for (Constraint c : consts) {
+						if (c.contains(line.getEndItem())) {
+							if (c.getConstraintType() == Constraint.VERTICAL) {
+								vConst.add(line.getStartItem());
+								vConst.add(line.getEndItem());
+							} else {
+								hConst.add(line.getStartItem());
+								hConst.add(line.getEndItem());
+							}
+						}
+					}
+				}
+			} else {
+				seen.add(i);
+			}
+		}
+
+		// only dots that are not line ends are copied
+		seen.removeAll(lineEnds);
+
+		// copy all single dots
+		for (Item i : seen) {
+			Item copy = i.copy();
+
+			if (i.getParent() != null) {
+				// if this is the frame name, make sure the frame is saved (in
+				// case it is a TDFC frame)
+				if (i == i.getParent().getName())
+					i.getParent().setChanged(true);
+
+				// if this is the title of the frame, link it to the frame
+				if (i.getLink() == null && i == i.getParent().getTitle()
+						&& toCopy.size() == 1) {
+					// save the frame after copying
+					i.getParent().setChanged(true);
+					copy.setLink(i.getParent().getFrameName());
+				}
+			}
+
+			copies.add(copy);
+		}
+
+		seen.clear();
+
+		// replace line ends with their copies
+		// this is done here so that copied lines can still share end points
+		for (Item i : lineEnds) {
+			// if a copy of this dot has not yet been created
+			// only copy dots that are in the list of dots to copy, leave
+			// others
+			// untouched
+			if (!seen.contains(i) && toCopy.contains(i)) {
+				// create a copy of the line end
+				Item copy = i.copy();
+
+				//copy.setID(DisplayIO.getCurrentFrame().getNextItemID());
+
+				copy.removeAllLines();
+				copy.removeAllConstraints();
+
+				// replace all other instances with the copy
+				ReplaceAll(lineEnds, i, copy);
+				ReplaceAll(vConst, i, copy);
+				ReplaceAll(hConst, i, copy);
+
+				seen.add(i);
+				copies.add(copy);
+			}
+		}
+
+		// recreate lines
+		for (int i = 0; i < lineEnds.size(); i += 2) {
+			Line line = (Line) lines.get(i / 2).copy();
+
+			line.setID(DisplayIO.getCurrentFrame().getNextItemID());
+			line.setStartItem(lineEnds.get(i));
+			line.setEndItem(lineEnds.get(i + 1));
+
+			copies.add(line);
+		}
+
+		// recreate vertical constraints
+		for (int i = 0; i < vConst.size(); i += 2) {
+			new Constraint(vConst.get(i), vConst.get(i + 1), DisplayIO
+					.getCurrentFrame().getNextItemID(), Constraint.VERTICAL);
+		}
+
+		// recreate horizontal constraints
+		for (int i = 0; i < hConst.size(); i += 2) {
+			new Constraint(hConst.get(i), hConst.get(i + 1), DisplayIO
+					.getCurrentFrame().getNextItemID(), Constraint.HORIZONTAL);
+		}
+
+		return copies;
+	}
+
+	private static void ReplaceAll(List<Item> list, Item replace, Item with) {
+		// replace all other instances with the copy
+		for (int pos = 0; pos < list.size(); pos++)
+			if (list.get(pos) == replace) {
+				list.set(pos, with);
+			}
+	}
+
+	/**
+	 * Attempts to create a new line that starts from the given Item
+	 * ('unreeling'). The Item must already have at least one line, and not be a
+	 * line itself to be unreeled from.
+	 * 
+	 * @param toUnreelFrom
+	 *            The Item that will be one end point of the new line
+	 * @return A List containing the newly created Item and Line that unreel
+	 *         from the given Item, or null if this Item cannot be unreeled
+	 *         from.
+	 */
+	public static List<Item> UnreelLine(Item toUnreelFrom) {
+		// the Item must already have one line to be unreeled from
+		if (toUnreelFrom == null || toUnreelFrom.getLines().size() < 1)
+			return null;
+
+		List<Item> unreel = new ArrayList<Item>(2);
+		unreel.add(toUnreelFrom);
+		unreel.addAll(toUnreelFrom.getLines());
+		return UnreelLine(unreel);
+	}
+
+	/**
+	 * Attempts to create a new line that starts from the given list of Items
+	 * ('unreeling'). The List must contain only one non-line Item. The non-line
+	 * Item must already have at least one line to be unreeled from.
+	 * 
+	 * @param toUnreel
+	 *            The List containing the Item that will be one end point of the
+	 *            new line
+	 * @return A List of the newly created Item and Line that unreel from the
+	 *         Item in the given List, or null if this List cannot be unreeled
+	 *         from.
+	 */
+	public static List<Item> UnreelLine(List<Item> toUnreel) {
+		Item origEnd = null;
+		// find the end being unreeled from
+		for (Item item : toUnreel) {
+			// we dont want to unreel anything other than lines
+			if (!(item instanceof Dot || item instanceof Line)) {
+				return null;
+			}
+			//find the dot to unreel from
+			if (item instanceof Dot) {
+				// if there are multiple ends in the list, return
+				if (origEnd != null)
+					return null;
+
+				origEnd = item;
+			}
+		}
+
+		// copy the original endpoint
+		Item copy = origEnd.copy();
+		copy.removeAllLines();
+		copy.removeAllConstraints();
+
+		int num = 0;
+
+		//Mike: WHAT IS THIS CODE DOING?!!?
+		for (Line line : origEnd.getLines())
+			num = Math.min(num, line.getID());
+
+		num--;
+
+		// create a new line
+		Line line = new Line(origEnd, copy, num);
+		//copy.setFloating(true);
+		origEnd.setArrowheadLength(0);
+		copy.setArrowheadLength(0);
+
+		List<Item> toReturn = new LinkedList<Item>();
+		toReturn.add(copy);
+		toReturn.add(line);
+		return toReturn;
+	}
+
+	/**
+	 * Checks through all Lines and Dots on the current Frame to detect if any
+	 * form an enclosure, which can then be used to manipulate items within the
+	 * polygon. If an enclosure is found, then the dots will have their
+	 * enclosure value set to true, and a List is created that contains all the
+	 * Dots in the order they were processed. Actual calculation of the Polygon
+	 * is done dynamically (to account for Dots being moved).
+	 */
+	public static void EnclosedCheck(List<Item> items) {
+		_seen.clear();
+
+		// get all dots on the Frame
+		List<Dot> dots = new ArrayList<Dot>(0);
+		for (Item i : items) {
+			if (i instanceof Dot) {
+				Dot d = (Dot) i;
+				d.setEnclosedList(null);
+
+				if (d.getLines().size() == 2)
+					dots.add(d);
+			}
+		}
+
+		// if there are no dots on the Frame, then there can't be an enclosure
+		if (dots.size() == 0)
+			return;
+
+		// iterate through all the dots
+		for (Dot searchFor : dots) {
+			_seen.clear();
+
+			for (Line l : searchFor.getLines()) {
+				_seen.add(l);
+				if (traverse(searchFor, l.getOppositeEnd(searchFor))) {
+					_path.add((Dot) l.getOppositeEnd(searchFor));
+
+					for (Dot d : _path)
+						d.setEnclosedList(_path);
+
+					_path = new ArrayList<Dot>(0);
+
+					break;
+				}
+			}
+		}
+
+	}
+
+	private static List<Line> _seen = new ArrayList<Line>();
+
+	private static List<Dot> _path = new ArrayList<Dot>();
+
+	private static boolean traverse(Dot toFind, Item searchFrom) {
+		if (toFind == null || searchFrom == null
+				|| !(searchFrom instanceof Dot))
+			return false;
+
+		if (searchFrom.getLines().size() != 2)
+			return false;
+
+		if (toFind == searchFrom)
+			return true;
+
+		for (Line l : searchFrom.getLines()) {
+			if (!(_seen.contains(l))) {
+				_seen.add(l);
+				if (traverse(toFind, l.getOppositeEnd(searchFrom))) {
+					_path.add((Dot) l.getOppositeEnd(searchFrom));
+					return true;
+				}
+			}
+
+		}
+
+		return false;
+	}
+}
Index: /trunk/src/org/expeditee/items/Line.java
===================================================================
--- /trunk/src/org/expeditee/items/Line.java	(revision 6)
+++ /trunk/src/org/expeditee/items/Line.java	(revision 6)
@@ -0,0 +1,897 @@
+package org.expeditee.items;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements a line that is drawn on the screen. A line is represented as two
+ * Items that share a common Line ID.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Line extends Item {
+
+	private static final int AUTO_ARROWHEAD_LENGTH = -1;
+	
+	private static final double DEFAULT_ARROWHEAD_RATIO = 0.5;
+
+	private static final int ARROW_HEAD_TO_LENGTH_RATIO = 8;
+
+	private static final int MINIMUM_ARROW_HEAD_LENGTH = 0;
+
+	public static final Float DEFAULT_THICKNESS = 2f;
+
+	public static final Float MINIMUM_THICKNESS = 1f;
+
+	private Item _start;
+
+	private Item _end;
+
+	// old point locations, used for clearing
+	private Point _startOffset = new Point(0, 0);
+
+	private Point _endOffset = new Point(0, 0);
+
+	// indicates which end the arrowhead should be drawn at
+	private Polygon _poly = null;
+
+	private float _thickness = -1;
+
+	private boolean _isCircle = false;
+
+	// brush strokes used for painting this line and highlighting
+	private Stroke _lineStroke = new BasicStroke(DEFAULT_THICKNESS,
+			BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+
+	// private Stroke _highlightStroke = new BasicStroke (1f + /*GRAVITY +
+	// (GRAVITY * (1 / */(2 *GRAVITY)/*))*/, BasicStroke.CAP_BUTT,
+	// BasicStroke.JOIN_ROUND);
+
+	/**
+	 * Constructs a new Line with the given start and end point and Item ID.
+	 * 
+	 * @param start
+	 *            The starting Point of this Line.
+	 * @param end
+	 *            The end Point of this Line.
+	 * @param id
+	 *            The Item ID of this Line.
+	 */
+	public Line(Item start, Item end, int id) {
+		super();
+		setID(id);
+		_start = start;
+		_end = end;
+
+		// update the two end points to add this line
+		_start.addLine(this);
+		_end.addLine(this);
+
+		// initialise the line size
+		float thick;
+		if (_start instanceof Dot)
+			thick = ((Dot) _start).getThickness();
+		else
+			thick = _start.getSize();
+
+		if (_end instanceof Dot)
+			thick += ((Dot) _end).getThickness();
+		else
+			thick += _end.getSize();
+
+		setLinePattern(_start.getLinePattern());
+		// Set the thickness to be half the value of the end points
+		setThickness(thick / 2.0f);
+	}
+
+	/**
+	 * Returns the Dot at the start of this Line.
+	 * 
+	 * @return The Dot that is the start of this line.
+	 */
+	public Item getStartItem() {
+		return _start;
+	}
+
+	/**
+	 * Returns the Dot that is the end of this Line.
+	 * 
+	 * @return The Dot that is the end of this line.
+	 */
+	public Item getEndItem() {
+		return _end;
+	}
+
+	/**
+	 * Returns the Dot at the opposite end to the Dot given. If the given Dot is
+	 * not part of this line, null is returned.
+	 * 
+	 * @param fromThis
+	 *            The Dot at the opposite end than this Dot is returned
+	 * @return The Dot at the opposite end of this Line to the given Dot, or
+	 *         Null if the given Dot is not on this Line.
+	 */
+	public Item getOppositeEnd(Item fromThis) {
+		if (_start == fromThis)
+			return _end;
+
+		if (_end == fromThis)
+			return _start;
+
+		return null;
+	}
+
+	/**
+	 * Replaces either the start or end point of this Line with the given Dot.
+	 * This is used when merging two Dots.
+	 * 
+	 * @param replace
+	 *            Either the start or end Dot of this Line indicating which is
+	 *            to be replaced.
+	 * @param with
+	 *            The Dot to replace the start or end Dot with.
+	 */
+	public void replaceEnd(Item replace, Item with) {
+		if (_start == replace)
+			setStartItem(with);
+		else
+			setEndItem(with);
+	}
+
+	public void setStartItem(Item start) {
+		if (start != null) {
+			_start.removeLine(this);
+
+			if (!(start instanceof Dot)) {
+				_startOffset.x = _start.getX() - start.getX();
+				_startOffset.y = _start.getY() - start.getY();
+			} else {
+				_startOffset.x = 0;
+				_startOffset.y = 0;
+			}
+
+			_start = start;
+			_start.addLine(this);
+			updatePolygon();
+		}
+	}
+
+	public void setEndItem(Item end) {
+		if (end != null) {
+			_end.removeLine(this);
+
+			if (!(end instanceof Dot)) {
+				_endOffset.x = _end.getX() - end.getX();
+				_endOffset.y = _end.getY() - end.getY();
+			} else {
+				_endOffset.x = 0;
+				_endOffset.y = 0;
+			}
+
+			_end = end;
+			_end.addLine(this);
+			updatePolygon();
+		}
+	}
+
+	/**
+	 * Returns the Y value of the start Dot of this Line. To determine the end
+	 * Dot Y value use getEndDot().getY()
+	 * 
+	 * @return The Y value of the starting Dot of this Line.
+	 * @see #getStartDot()
+	 * @see #getEndDot()
+	 */
+	public int getY() {
+		return _start.getY();
+	}
+
+	/**
+	 * Returns the X value of the start Dot of this Line. To determine the end
+	 * Dot X value use getEndDot().getX()
+	 * 
+	 * @return The X value of the starting Dot of this Line.
+	 * @see #getStartDot()
+	 * @see #getEndDot()
+	 */
+	public int getX() {
+		// return 0;
+		return _start.getX();
+	}
+
+	public void setX(int x) {
+	}
+
+	public void setY(int y) {
+	}
+
+	@Override
+	public void setPosition(int x, int y) {
+	}
+
+	@Override
+	public String getLink() {
+		return null;
+	}
+
+	private void paintCircle(Graphics2D g, Item end) {
+		int distance = (int) Math.sqrt(Math.pow(Math.abs(_start.getX()
+				- _end.getX()), 2)
+				+ Math.pow(Math.abs(_start.getY() - _end.getY()), 2));
+
+		// Approximation of circle for mouse interaction
+		int points = 20;
+
+		double radians = 0.0;
+		int xPoints[] = new int[points];
+		int yPoints[] = new int[xPoints.length];
+
+		for (int i = 0; i < xPoints.length; i++) {
+			xPoints[i] = (int) (distance * Math.cos(radians));
+			yPoints[i] = (int) (distance * Math.sin(radians));
+			radians += (2.0 * Math.PI) / xPoints.length;
+		}
+
+		_poly = new Polygon(xPoints, yPoints, xPoints.length);
+		_poly.translate(end.getX(), end.getY());
+
+		distance *= 2;
+
+		g.drawOval(end.getX() - (distance / 2), end.getY() - (distance / 2),
+				distance, distance);
+		// Arc version, same result but allows for portions of the circle to be
+		// drawn
+		// g.drawArc(end.getX() - (distance / 2), end.getY() - (distance / 2),
+		// distance, distance, 0, 360);
+	}
+
+	public void setLinePattern(int[] pattern) {
+		super.setLinePattern(pattern);
+
+		float thick = getThickness();
+
+		if (thick < 0)
+			thick = DEFAULT_THICKNESS;
+
+		if (pattern != null) {
+			float[] dash = new float[pattern.length];
+			for (int i = 0; i < pattern.length; i++)
+				dash[i] = (float) pattern[i];
+			_lineStroke = new BasicStroke(Math.max(thick, MINIMUM_THICKNESS),
+					BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, dash,
+					0.0f);
+		} else
+			_lineStroke = new BasicStroke(Math.max(thick, MINIMUM_THICKNESS),
+					BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+
+		if (_start.getLinePattern() != pattern)
+			_start.setLinePattern(pattern);
+
+		if (_end.getLinePattern() != pattern)
+			_end.setLinePattern(pattern);
+	}
+
+	@Override
+	public void paint(Graphics2D g) {
+		g.setColor(getPaintColor());
+
+		if (_start instanceof Text && ((Text) _start).startsWith("@c")) {
+			g.setStroke(_lineStroke);
+			paintCircle(g, _end);
+			_isCircle = true;
+		} else if (_end instanceof Text && ((Text) _end).startsWith("@c")) {
+			g.setStroke(_lineStroke);
+			paintCircle(g, _start);
+			_isCircle = true;
+		} else {
+			// paint the arrowhead (if necessary)
+			g.setStroke(new BasicStroke(getThickness(), BasicStroke.CAP_BUTT,
+					BasicStroke.JOIN_MITER));
+
+			if (_start.isFloating() && !_end.isFloating()) {
+				paintArrow(g, _start, _startOffset, _endOffset);
+			} else if (!_start.isFloating() && _end.isFloating()) {
+				paintArrow(g, _end, _endOffset, _startOffset);
+			} else {
+				paintArrow(g, _start, _startOffset, _endOffset);
+				paintArrow(g, _end, _endOffset, _startOffset);
+			}
+
+			g.setStroke(_lineStroke);
+			g.drawLine(_start.getX() + _startOffset.x, _start.getY()
+					+ _startOffset.y, _end.getX() + _endOffset.x, _end.getY()
+					+ _endOffset.y);
+
+			_isCircle = false;
+		}
+
+		// update the highlight shape
+		// java.awt.geom.Line2D.Float shape = new
+		// java.awt.geom.Line2D.Float(_startx, _starty, _endx, _endy);
+		// _shape = _highlightStroke.createStrokedShape(shape);
+
+		// g.setStroke(norm);
+
+		if (showLineHighlight() && isHighlighted()) {
+			g.setColor(getHighlightColor());
+			((Graphics2D) g).draw(this.getArea());
+		}
+
+		if (_virtualSpot != null) {
+			_virtualSpot.paint(g);
+			_virtualSpot = null;
+		}
+	}
+
+	/**
+	 * Based on code from DeSL (Arrow2D) http://sourceforge.net/projects/desl/
+	 */
+	private void paintArrow(Graphics2D g, Item withArrow, Point startOffset,
+			Point endOffset) {
+		// only draw an arrowhead if necessary
+		if (!withArrow.hasVisibleArrow() || withArrow.getLines().size() > 1)
+			return;
+
+		int x0, x1, y0, y1;
+
+		x1 = withArrow.getX() + startOffset.x;
+		y1 = withArrow.getY() + startOffset.y;
+		x0 = getOppositeEnd(withArrow).getX() + endOffset.x;
+		y0 = getOppositeEnd(withArrow).getY() + endOffset.y;
+
+		int arrowLength = withArrow.getArrowheadLength();
+		double arrowRatio = withArrow.getArrowheadRatio();
+
+		// if the arrowhead is 'auto', then one and only one end must be
+		// floating
+		if (arrowLength != AUTO_ARROWHEAD_LENGTH && arrowRatio < 0)
+			return;
+
+		int deltaX = x1 - x0;
+		int deltaY = y1 - y0;
+
+		int length = (int) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
+
+		if (arrowLength < 0) {
+			arrowLength = length / ARROW_HEAD_TO_LENGTH_RATIO;
+			arrowLength = Math.max(MINIMUM_ARROW_HEAD_LENGTH, arrowLength);
+			withArrow.setArrowhead(null);
+			if (arrowRatio < 0)
+				arrowRatio = DEFAULT_ARROWHEAD_RATIO;
+		}
+
+		// only calculate the arrowhead polygon if necessary
+		if (withArrow.getArrowhead() == null) {
+			Polygon arrowhead = new Polygon();
+			arrowhead.addPoint((int) x0, (int) y0);
+			arrowhead.addPoint((int) x0 - arrowLength,
+					(int) (y0 - (arrowLength * arrowRatio)));
+			arrowhead.addPoint((int) x0, (int) y0);
+			arrowhead.addPoint((int) x0 - arrowLength,
+					(int) (y0 + (arrowLength * arrowRatio)));
+			withArrow.setArrowhead(arrowhead);
+		}
+
+		Polygon arrowhead = withArrow.getArrowhead();
+		double rad = calcAngle((float) x0, (float) y0, (float) x1, (float) y1);
+		arrowhead.translate((x0 + length) - arrowhead.xpoints[0], y0
+				- arrowhead.ypoints[0]);
+		AffineTransform tx = AffineTransform.getRotateInstance(rad, x0, y0);
+		g.draw(tx.createTransformedShape((Shape) arrowhead));
+	}
+
+	/**
+	 * Calcuates the angle of rotation (in radians) of the arrowhead Based on
+	 * code from DeSL (Arrow2D) http://sourceforge.net/projects/desl/
+	 */
+	private double calcAngle(float x1, float y1, float x2, float y2) {
+		double rad = 0.0d;
+		float dx = x2 - x1;
+		float dy = y2 - y1;
+
+		if (dx == 0.0) {
+			if (dy == 0.0) {
+				rad = 0.0;
+			} else if (dy > 0.0) {
+				rad = Math.PI / 2.0;
+			} else {
+				rad = Math.PI * 3.0 / 2.0;
+			}
+		} else if (dy == 0.0) {
+			if (dx > 0.0) {
+				rad = 0.0;
+			} else {
+				rad = Math.PI;
+			}
+		} else {
+			if (dx < 0.0) {
+				rad = Math.atan(dy / dx) + Math.PI;
+			} else if (dy < 0.0) {
+				rad = Math.atan(dy / dx) + (2 * Math.PI);
+			} else {
+				rad = Math.atan(dy / dx);
+			}
+		}
+
+		return rad;
+	}
+
+	@Override
+	public Polygon getPolygon() {
+		if (_poly == null)
+			updatePolygon();
+
+		return _poly;
+	}
+
+	@Override
+	public Line copy() {
+		Item s = _start.copy();
+		Item e = _end.copy();
+
+		Line temp = new Line(s, e, getID());
+
+		Item.DuplicateItem(this, temp);
+
+		temp.setLinePattern(getLinePattern());
+		temp.setThickness(getThickness());
+
+		temp.setArrow(getArrowheadLength(), getArrowheadRatio());
+
+		return temp;
+	}
+
+	@Override
+	public int showHighlight(boolean val) {
+		super.showHighlight(val);
+
+		return Item.UNCHANGED_CURSOR;
+	}
+
+	@Override
+	public void setMaxSize(Dimension d) {
+	}
+
+	/**
+	 * Sets the Color of this Line and the two Dots at the ends.
+	 * 
+	 * @param c
+	 *            The Color to paint this Line in.
+	 */
+	@Override
+	public void setColor(Color c) {
+		super.setColor(c);
+
+		if (_start.getColor() != c)
+			_start.setColor(c);
+
+		if (_end.getColor() != c)
+			_end.setColor(c);
+	}
+
+	@Override
+	public int getSize() {
+		return _start.getSize();
+	}
+
+	@Override
+	public void setSize(int size) {
+	}
+
+	@Override
+	public void setThickness(float thick) {
+		_thickness = thick;
+
+		if (thick < 0)
+			thick = DEFAULT_THICKNESS;
+
+		if (_start instanceof Dot) {
+			Dot start = (Dot) _start;
+			if (start.getThickness() != thick)
+				start.setThickness(thick);
+		}
+
+		if (_end instanceof Dot) {
+			Dot end = (Dot) _end;
+			if (end.getThickness() != thick)
+				end.setThickness(thick);
+		}
+
+		int[] pattern = _start.getLinePattern();
+		if (pattern == null)
+			pattern = _end.getLinePattern();
+
+		if (pattern != null) {
+			float[] dash = new float[pattern.length];
+			for (int i = 0; i < pattern.length; i++)
+				dash[i] = (float) pattern[i];
+			_lineStroke = new BasicStroke(Math.max(thick, MINIMUM_THICKNESS),
+					BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, dash,
+					0.0f);
+		} else
+			_lineStroke = new BasicStroke(Math.max(thick, MINIMUM_THICKNESS),
+					BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+	}
+
+	@Override
+	public float getThickness() {
+		return _thickness;
+	}
+
+	@Override
+	public void setAnnotation(boolean val) {
+	}
+
+	@Override
+	public boolean isAnnotation() {
+		return false;
+	}
+
+	/**
+	 * Fixes the length of the arrowhead on the end of the line.
+	 */
+	public void fixArrowheadLength() {
+		if (_end.getArrowheadLength() == AUTO_ARROWHEAD_LENGTH) {
+			fixArrowheadLength(_end);
+		}
+	}
+	
+	public void autoArrowheadLength() {
+		_end.setArrowheadLength(AUTO_ARROWHEAD_LENGTH);
+		_end.setArrowheadRatio(DEFAULT_ARROWHEAD_RATIO);
+	}
+
+	public void fixArrowheadLength(Item arrow) {
+		// if the arrow isnt there turn it on!
+		int x0, x1, y0, y1;
+
+		x1 = arrow.getX();
+		y1 = arrow.getY();
+		x0 = getOppositeEnd(arrow).getX();
+		y0 = getOppositeEnd(arrow).getY();
+
+		int deltaX = x1 - x0;
+		int deltaY = y1 - y0;
+
+		int length = (int) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
+		length /= ARROW_HEAD_TO_LENGTH_RATIO;
+		arrow.setArrowheadLength(Math.max(MINIMUM_ARROW_HEAD_LENGTH, length));
+	}
+
+	public void toggleArrow() {
+		Item arrow;
+		if (_start.isFloating() && !_end.isFloating())
+			arrow = _start;
+		else if (_end.isFloating() && !_start.isFloating())
+			arrow = _end;
+		else
+			return;
+
+		// if there already an arrow, turn it off
+		if (arrow.getArrowheadLength() != 0 && arrow.getArrowheadRatio() > 0) {
+			arrow.setArrowheadLength(0);
+			return;
+		}
+
+		fixArrowheadLength(arrow);
+	}
+
+	private static int[] LinePatterns = new int[] { 0, 10, 20 };
+
+	/**
+	 * The rotates through a wheel of dashed lines.
+	 * 
+	 * @param amount
+	 *            number of rotations around the wheel to toggle by.
+	 */
+	public void toggleDashed(int amount) {
+		// find the index of the current line pattern
+		int[] currentPattern = getLinePattern();
+
+		// Find the current pattern and move to the next pattern in the wheel
+		for (int i = 0; i < LinePatterns.length; i++) {
+			if (currentPattern == null || currentPattern[0] == LinePatterns[i]) {
+				i += LinePatterns.length + amount;
+				i %= LinePatterns.length;
+
+				// if we are at the start of the wheel make it 'null' (solid
+				// line)
+				if (i == 0) {
+					setLinePattern(null);
+				} else {
+					setLinePattern(new int[] { LinePatterns[i], LinePatterns[i] });
+				}
+				return;
+			}
+		}
+	}
+
+	private static double[] ArrowHeadRatios = new double[] { 0.25, 0.5, 1.0 };
+
+	/**
+	 * This will toggle the arrow head between the various types of UML arrow
+	 * heads.
+	 * 
+	 * @param amount
+	 */
+	public void toggleArrowHeadRatio(int amount) {
+		Item arrow = null;
+		if (_start.isFloating() && !_end.isFloating())
+			arrow = _start;
+		else if (_end.isFloating() && !_start.isFloating())
+			arrow = _end;
+		else
+			return;
+
+		// find the index of the current line pattern
+		double currentRatio = arrow.getArrowheadRatio();
+
+		// Find the current pattern and move to the next pattern in the wheel
+		for (int i = 0; i < ArrowHeadRatios.length; i++) {
+			if (currentRatio - ArrowHeadRatios[i] < 0.001) {
+				i += ArrowHeadRatios.length + amount;
+				i %= ArrowHeadRatios.length;
+				arrow.setArrowheadRatio(ArrowHeadRatios[i]);
+				return;
+			}
+		}
+	}
+
+	private static int[] ArrowHeadLength = new int[] { /* AUTO_ARROWHEAD_LENGTH, */
+	0, 20, 40, 80 };
+
+	/**
+	 * Changes the length of the arrow head. In the future this may toggle the
+	 * arrow head between the various types of UML arrow heads.
+	 * 
+	 * @param amount
+	 */
+	public void toggleArrowHeadLength(int amount) {
+		Item arrow = null;
+		if (_start.isFloating() && !_end.isFloating())
+			arrow = _start;
+		else if (_end.isFloating() && !_start.isFloating())
+			arrow = _end;
+		else
+			return;
+
+		// find the index of the current line pattern
+		int currentLength = arrow.getArrowheadLength();
+
+		// Find the current pattern and move to the next pattern in the wheel
+		for (int i = 0; i < ArrowHeadLength.length; i++) {
+			if (currentLength <= ArrowHeadLength[i]) {
+				i += ArrowHeadLength.length + amount;
+				i %= ArrowHeadLength.length;
+				arrow.setArrowheadLength(ArrowHeadLength[i]);
+				return;
+			}
+		}
+	}
+
+	private Dot _virtualSpot = null;
+
+	public void showVirtualSpot(Dot orig, int mouseX, int mouseY) {
+		if (orig.getLines().size() != 1)
+			return;
+
+		// lines that are in 'connected' mode, also cannot be attached
+		if (orig.getLines().get(0).getOppositeEnd(orig).isFloating())
+			return;
+
+		Dot spot = orig.copy();
+		spot.setThickness(Math.max(this.getThickness(), 5));
+		if (this.getColor() != Color.RED)
+			spot.setColor(Color.RED);
+		else
+			spot.setColor(Color.BLUE);
+
+		// calculate nearest point on line from spot
+		double slope1;
+		double slope2;
+		double x, y;
+
+		slope1 = (_start.getY() - _end.getY() * 1.0)
+				/ (_start.getX() - _end.getX());
+		slope2 = -1 / slope1;
+
+		// if the line is horizontal
+		if (slope1 == 0) {
+			x = spot.getX();
+			y = _start.getY();
+			// if the line is vertical
+		} else if (slope2 == 0) {
+			x = _start.getX();
+			y = spot.getY();
+			// otherwise, the line is sloped
+		} else {
+			x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
+					* _start.getX())
+					/ (slope1 - slope2);
+			y = slope1 * (x - _start.getX()) + _start.getY();
+		}
+
+		// position spot on the line
+		spot.setPosition((int) x, (int) y);
+		_virtualSpot = spot;
+	}
+
+	@Override
+	public Item merge(Item merger, int mouseX, int mouseY) {
+		if (!(merger instanceof Dot))
+			return merger;
+
+		Dot spot = (Dot) merger;
+
+		// dots with multiple lines cannot be attached, nor can dots with no
+		// lines
+		if (spot.getLines().size() != 1)
+			return merger;
+
+		// lines that are in 'connected' mode, also cannot be attached
+		if (spot.getLines().get(0).getOppositeEnd(spot).isFloating())
+			return merger;
+
+		if (spot.getConstraints() != null)
+			spot.getConstraints().clear();
+
+		if (_end.getConstraints() != null)
+			_end.getConstraints().clear();
+
+		if (_start.getConstraints() != null)
+			_start.getConstraints().clear();
+
+		// calculate nearest point on line from spot
+		double slope1;
+		double slope2;
+		double x, y;
+
+		slope1 = (_start.getY() - _end.getY() * 1.0)
+				/ (_start.getX() - _end.getX());
+		slope2 = -1 / slope1;
+
+		// if the line is horizontal
+		if (slope1 == 0) {
+			x = merger.getX();
+			y = _start.getY();
+			// if the line is vertical
+		} else if (slope2 == 0) {
+			x = _start.getX();
+			y = merger.getY();
+			// otherwise, the line is sloped
+		} else {
+			x = (-1 * slope2 * spot.getX() + spot.getY() - _start.getY() + slope1
+					* _start.getX())
+					/ (slope1 - slope2);
+			y = slope1 * (x - _start.getX()) + _start.getY();
+		}
+
+		// position spot on the line
+		spot.setPosition((int) x, (int) y);
+
+		Line temp = copy();
+
+		temp.setID(-1);
+		temp.setEndItem(_end);
+		temp.setStartItem(spot);
+
+		setEndItem(spot);
+
+		// if(_arrowEnd == spot)
+		// setArrow(-1,-1);
+
+		return spot;
+	}
+
+	@Override
+	public List<Item> getConnected() {
+		return getAllConnected();
+	}
+
+	public List<Item> getAllConnected() {
+		ArrayList<Item> list = new ArrayList<Item>();
+		addAllConnected(list);
+		return list;
+	}
+
+	public void addAllConnected(List<Item> connected) {
+		if (!connected.contains(this))
+			connected.add(this);
+
+		if (!connected.contains(_end))
+			_end.addAllConnected(connected);
+
+		if (!connected.contains(_start))
+			_start.addAllConnected(connected);
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof Line))
+			return false;
+
+		return ((Item) o).getID() == getID();
+	}
+
+	@Override
+	public void setAction(List<String> actions) {
+	}
+
+	@Override
+	public List<String> getAction() {
+		return null;
+	}
+
+	public String getLineEnds() {
+		return _start.getID() + " " + _end.getID();
+	}
+
+	public int getLineType() {
+		return 1;
+	}
+
+	@Override
+	protected void updatePolygon() {
+		if (_isCircle) {
+
+		}
+
+		_poly = new Polygon();
+
+		Rectangle one;
+		Rectangle two;
+
+		/**
+		 * TODO: Fake dots seems inefficient, Fix it.
+		 */
+
+		// line goes from left->right, one->two
+		if (_start.getX() <= _end.getX()) {
+			/*
+			 * one = _start.getPolygon().getBounds(); two =
+			 * _end.getPolygon().getBounds();
+			 */
+
+			Dot s = new Dot(_start.getX(), _start.getY(), -1);
+			Dot e = new Dot(_end.getX(), _end.getY(), -1);
+
+			one = s.getPolygon().getBounds();
+			two = e.getPolygon().getBounds();
+		} else {
+			Dot s = new Dot(_start.getX(), _start.getY(), -1);
+			Dot e = new Dot(_end.getX(), _end.getY(), -1);
+
+			one = e.getPolygon().getBounds();
+			two = s.getPolygon().getBounds();
+			/*
+			 * one = _end.getPolygon().getBounds(); two =
+			 * _start.getPolygon().getBounds();
+			 */
+		}
+
+		// if one is above two
+		if (one.getY() < two.getY()) {
+			_poly.addPoint((int) one.getMaxX(), (int) one.getMinY());
+			_poly.addPoint((int) two.getMaxX(), (int) two.getMinY());
+			_poly.addPoint((int) two.getMinX(), (int) two.getMaxY());
+			_poly.addPoint((int) one.getMinX(), (int) one.getMaxY());
+			// if one is below two
+		} else {
+			_poly.addPoint((int) one.getMinX(), (int) one.getMinY());
+			_poly.addPoint((int) two.getMinX(), (int) two.getMinY());
+			_poly.addPoint((int) two.getMaxX(), (int) two.getMaxY());
+			_poly.addPoint((int) one.getMaxX(), (int) one.getMaxY());
+		}
+	}
+}
Index: /trunk/src/org/expeditee/items/Picture.java
===================================================================
--- /trunk/src/org/expeditee/items/Picture.java	(revision 6)
+++ /trunk/src/org/expeditee/items/Picture.java	(revision 6)
@@ -0,0 +1,549 @@
+package org.expeditee.items;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.image.ImageObserver;
+import java.io.File;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.io.Logger;
+
+/**
+ * This class represents an Image loaded from a file which is shown on the
+ * screen. Loading of the Image from disk occurs in the constructor, and takes
+ * approximately one second per mb of the Image file size. <br>
+ * <br>
+ * Currently Supported (Tested) Image formats:<br>
+ * BMP<br>
+ * JPG<br>
+ * GIF<br>
+ * GIF (Animated)<br>
+ * <br>
+ * Currently only the default size of the Image is supported, but future
+ * versions may support scaling.
+ * 
+ * @author jdm18
+ * 
+ */
+public class Picture extends Item {
+
+	public static final int WIDTH = 0;
+
+	public static final int RATIO = 1;
+
+	public static final int FRACTION = 2;
+
+	private Image _image = null;
+
+	private Polygon _poly = null;
+
+	// private Polygon _circle = null;
+	private Text _source = null;
+
+	// true if this is a frame picture
+	private boolean _isFrame = false;
+
+	private int _numerator = -1;
+
+	private int _denominator = -1;
+
+	private float _ratio = 1.0f;
+
+	private int _width = -1;
+
+	private int _scaleType = RATIO;
+
+	private float _scale = 1.0f;
+
+	private Point _cropStart = null;
+
+	private Point _cropEnd = null;
+
+	private Point _cropOrigin = null;
+
+	private boolean _showCropping = false;
+
+	// used to repaint animated GIF images, among other things.
+	private ImageObserver _imageObserver = null;
+
+	/**
+	 * Creates a new Picture from the given path. The ImageObserver is optional
+	 * and can be set to NULL. <br>
+	 * Note: It is assumed that the file described in path has already been
+	 * checked to exist.
+	 * 
+	 * @param source
+	 *            The Text Item that was used to create this Picture
+	 * @param path
+	 *            The Path of the Image to load from disk.
+	 * @param observer
+	 *            The ImageObserver to assign when painting the Image on the
+	 *            screen.
+	 */
+	public Picture(Text source, String path, String size, ImageObserver observer)
+			throws IllegalArgumentException {
+		super();
+		_source = source;
+		_imageObserver = observer;
+
+		// Check if the picture is being created with @f
+		if (path == null && source.getLink() != null) {
+			String link = source.getLink();
+			if (FrameIO.isPositiveInteger(link)){
+				link = source.getParent().getFramesetNameAdjusted() + link;
+			}
+			Frame frame = FrameIO.LoadFrame(link);
+			FrameGraphics.UpdateBuffer(frame, false);
+			_image = frame.getBuffer();
+			_isFrame = true;
+
+		} else {
+
+			// ImageIcon is faster, but cannot handle some formats
+			// (notably
+			// .bmp)
+			// hence, we try this first, then if it fails we try
+			// ImageIO
+			_image = new ImageIcon(path).getImage();
+
+			// if ImageIcon failed to read the image
+			if (_image.getWidth(null) <= 0) {
+				try {
+					_image = ImageIO.read(new File(path));
+				} catch (IOException e) {
+					// e.printStackTrace();
+					Logger.Log(e);
+					_image = null;
+				}
+			}
+		}
+
+		if (_image == null)
+			throw new IllegalArgumentException();
+
+		try {
+			if (size.length() == 0) {
+				size = "" + _image.getWidth(null);
+				_source.setText(_source.getFirstLine() + " " + size);
+			}
+
+			// parse size from text
+			if (size.contains("/")) {
+				// this is a fraction
+				_numerator = Integer.parseInt(size.substring(0, size
+						.indexOf("/")));
+				_denominator = Integer.parseInt(size.substring(size
+						.indexOf("/") + 1));
+				_scale = (_numerator * 1.0f) / _denominator;
+				_scaleType = FRACTION;
+			} else if (size.contains(".")) {
+				// this is a ratio
+				_ratio = Float.parseFloat(size);
+				_scaleType = RATIO;
+				_scale = _ratio;
+			} else if (size.length() > 0) {
+				// this is an absolute width
+				_width = Integer.parseInt(size);
+				_scaleType = WIDTH;
+				_scale = _width / (_image.getWidth(null) * 1.0f);
+			}
+		} catch (Exception e) {
+			size = "" + _image.getWidth(null);
+			_source.setText("@f " + size);
+			FrameGraphics.ErrorMessage("Invalid argument for @f tag");
+		}
+
+	}
+
+	public void setStartCrop(int x, int y) {
+		_cropStart = new Point(x - getX(), y - getY());
+		_cropOrigin = new Point(getX(), getY());
+	}
+
+	public void setEndCrop(int x, int y) {
+		_cropEnd = new Point(x - getX(), y - getY());
+	}
+
+	public void setShowCrop(boolean value) {
+		_showCropping = value;
+	}
+
+	public int getCroppedSize() {
+		if (_cropStart == null || _cropEnd == null)
+			return 0;
+
+		int diff = (_cropEnd.x - _cropStart.x);
+		diff *= (_cropEnd.y - _cropStart.y);
+		return diff;
+	}
+
+	public void clearCropping() {
+		_cropStart = null;
+		_cropEnd = null;
+		setShowCrop(false);
+	}
+
+	protected void updatePolygon() {
+		if (_image == null)
+			return;
+
+		_poly = new Polygon();
+
+		if (_cropStart == null || _cropEnd == null) {
+			int width = _image.getWidth(null);
+			int height = _image.getHeight(null);
+
+			width *= _scale;
+			height *= _scale;
+
+			int xdiff = 0;
+
+			if (getLink() != null || getAction() != null) {
+				xdiff -= 10;
+			}
+
+			// extra pixel around the image so the highlighting is visible
+			_poly.addPoint(_source.getX() - 1 + xdiff, _source.getY() - 1);
+			_poly.addPoint(_source.getX() + 1 + width, _source.getY() - 1);
+			_poly.addPoint(_source.getX() + 1 + width, _source.getY() + 1
+					+ height);
+			_poly.addPoint(_source.getX() - 1 + xdiff, _source.getY() + 1
+					+ height);
+		} else {
+			Rectangle clip = new Rectangle(_source.getX() + _cropStart.x
+					+ _cropOrigin.x, _source.getY() + _cropStart.y
+					+ _cropOrigin.y, _cropEnd.x - _cropStart.x, _cropEnd.y
+					- _cropStart.y).getBounds();
+			_poly.addPoint((int) clip.getMinX() - 1, (int) clip.getMinY() - 1);
+			_poly.addPoint((int) clip.getMinX() - 1, (int) clip.getMaxY() + 1);
+			_poly.addPoint((int) clip.getMaxX() + 1, (int) clip.getMaxY() + 1);
+			_poly.addPoint((int) clip.getMaxX() + 1, (int) clip.getMinY() - 1);
+
+		}
+	}
+
+	/**
+	 * Returns the Text Item that was used to create this Picture object. This
+	 * is required by KMSWriter for saving the Frame.
+	 * 
+	 * @return The Text Item used to create this Picture.
+	 */
+	public Text getText() {
+		return _source;
+	}
+
+	@Override
+	public int getID() {
+		return _source.getID();
+	}
+
+	@Override
+	public void setID(int newID) {
+		_source.setID(newID);
+	}
+
+	@Override
+	public int getY() {
+		return _source.getY();
+	}
+
+	@Override
+	public int getX() {
+		return _source.getX();
+	}
+
+	@Override
+	public void setPosition(int x, int y) {
+		_source.setPosition(x, y);
+
+		updatePolygon();
+	}
+
+	@Override
+	public String getLink() {
+		if (_source == null)
+			return null;
+		else
+			return _source.getLink();
+	}
+
+	@Override
+	public void setLink(String frameName) {
+		if (_source == null)
+			return;
+		else {
+			_source.setLink(frameName);
+
+			if (_isFrame) {
+				Frame frame = FrameIO.LoadFrame(_source.getLink());
+				// if (frame.getBuffer() == null)
+				FrameGraphics.UpdateBuffer(frame, false);
+				_image = frame.getBuffer();
+			}
+		}
+
+		updatePolygon();
+	}
+
+	@Override
+	public void setAction(List<String> action) {
+		if (_source == null)
+			return;
+		else
+			_source.setAction(action);
+
+		updatePolygon();
+	}
+
+	@Override
+	public List<String> getAction() {
+		if (_source == null)
+			return null;
+		else
+			return _source.getAction();
+	}
+
+	@Override
+	public void paint(Graphics2D g) {
+		if (_image == null)
+			return;
+
+		int width = _image.getWidth(null);
+		int height = _image.getHeight(null);
+
+		width *= _scale;
+		height *= _scale;
+
+		if (getLink() != null || getAction() != null) {
+			g.setColor(getPaintHighlightColor());
+
+			AffineTransform at = new AffineTransform();
+			AffineTransform orig = g.getTransform();
+			at.translate(getX() - 12, getY() + (getBoundsHeight() / 2));
+			g.setTransform(at);
+
+			if (getLinkMark() && getLink() != null) {
+				g.drawPolygon(getCircle());
+
+				// if the link is not valid, cross out the circle
+				if (!isLinkValid())
+					g.drawPolygon(getCircleCross());
+			}
+
+			if (getActionMark() && getAction() != null) {
+				g.drawPolygon(getCircle());
+				g.fillPolygon(getCircle());
+
+				// if the link is not valid, cross out the circle
+				if (getLink() != null && !isLinkValid()) {
+					g.setColor(getPaintBackgroundColor());
+					g.drawPolygon(getCircleCross());
+				}
+			}
+			g.setTransform(orig);
+		}
+
+		if (isHighlighted()) {
+			g.setColor(getHighlightColor());
+			g.drawPolygon(getPolygon());
+		}
+
+		// if we are showing the cropping, then show the original as transparent
+		if (_showCropping && _cropStart != null && _cropEnd != null) {
+			// show the full image as transparent
+			float alpha = .5f;
+			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+					alpha));
+			g.drawImage(_image, _source.getX(), _source.getY(), width, height,
+					_imageObserver);
+
+			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+					1.0f));
+			// show the cropped area normally
+			Shape clip = new Rectangle(_source.getX() + _cropStart.x, _source
+					.getY()
+					+ _cropStart.y, _cropEnd.x - _cropStart.x, _cropEnd.y
+					- _cropStart.y);
+			g.setColor(getPaintHighlightColor());
+			g.draw(clip);
+			g.setClip(clip);
+
+			g.drawImage(_image, _source.getX(), _source.getY(), width, height,
+					_imageObserver);
+			g.draw(clip);
+			// if the image is cropped, but we are not showing the cropping
+		} else if (_cropStart != null && _cropEnd != null) {
+			Shape clip = new Rectangle(_source.getX() + _cropStart.x
+					+ _cropOrigin.x, _source.getY() + _cropStart.y
+					+ _cropOrigin.y, _cropEnd.x - _cropStart.x, _cropEnd.y
+					- _cropStart.y);
+			g.setClip(clip);
+
+			g.drawImage(_image, _source.getX(), _source.getY(), width, height,
+					_imageObserver);
+
+			// otherwise, paint normally
+		} else {
+			g.drawImage(_image, _source.getX(), _source.getY(), width, height,
+					_imageObserver);
+		}
+	}
+
+	@Override
+	public Polygon getPolygon() {
+		if (_poly == null)
+			updatePolygon();
+
+		Polygon external = new Polygon(_poly.xpoints, _poly.ypoints,
+				_poly.npoints);
+		return external;
+	}
+
+	@Override
+	public int showHighlight(boolean val) {
+		super.showHighlight(val);
+
+		return Item.DEFAULT_CURSOR;
+	}
+
+	@Override
+	public Picture copy() {
+		Picture p;
+
+		if (_isFrame){
+			p = ItemUtils.CreateFramePicture((Text) _source.copy(),
+					_imageObserver);
+		}else
+			p = ItemUtils.CreatePicture((Text) _source.copy(), _imageObserver);
+
+		Item.DuplicateItem(this, p);
+
+		if (_cropStart != null)
+			p.setStartCrop(_cropStart.x, _cropStart.y);
+
+		if (_cropEnd != null)
+			p.setEndCrop(_cropEnd.x, _cropEnd.y);
+
+		return p;
+	}
+
+	@Override
+	public void setMaxSize(Dimension d) {
+		// TODO Auto-generated method stub
+	}
+
+	@Override
+	public Color getColor() {
+		return _source.getColor();
+	}
+
+	@Override
+	public void setColor(Color c) {
+		_source.setColor(c);
+	}
+
+	@Override
+	public int getSize() {
+		return _source.getSize();
+	}
+
+	@Override
+	public void setSize(int size) {
+		int diff = size - _source.getSize();
+		float oldScale = _scale;
+
+		int oldDenom = _denominator;
+		int oldNumer = _numerator;
+		float oldRatio = _ratio;
+		int oldWidth = _width;
+
+		switch (_scaleType) {
+		case (FRACTION):
+			_numerator += diff;
+			_scale = (_numerator * 1.0f) / _denominator;
+			break;
+		case (RATIO):
+			_ratio += (diff) * 0.05;
+			_scale = _ratio;
+			break;
+		case (WIDTH):
+			_width += 10 * diff;
+			_scale = _width / (_image.getWidth(null) * 1.0f);
+			break;
+		}
+
+		// picture must still be at least 10 pixels wide
+		if ((_scale * _image.getWidth(null)) <= 10) {
+			_scale = oldScale;
+
+			_denominator = oldDenom;
+			_numerator = oldNumer;
+			_ratio = oldRatio;
+			_width = oldWidth;
+		}
+
+		// update source text item
+		String line = _source.getFirstLine();
+
+		switch (_scaleType) {
+		case (FRACTION):
+			line = line.replace(oldNumer + "/" + oldDenom, _numerator + "/"
+					+ _denominator);
+			break;
+		case (RATIO):
+			DecimalFormat format = new DecimalFormat("0.00");
+			line = line.replace(format.format(oldRatio), format.format(_ratio));
+			break;
+		case (WIDTH):
+			line = line.replace("" + oldWidth, "" + _width);
+			break;
+		}
+
+		_source.setText(line);
+		updatePolygon();
+	}
+
+	@Override
+	public void setAnnotation(boolean val) {
+		// TODO Auto-generated method stub
+
+	}
+
+	@Override
+	public boolean isAnnotation() {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	/**
+	 * Returns the Image that this Picture object is painting on the screen.
+	 * This is used by Frame to repaint animated GIFs.
+	 * 
+	 * @return The Image that this Picture object represents.
+	 */
+	public Image getImage() {
+		return _image;
+	}
+
+	@Override
+	public Item merge(Item merger, int mouseX, int mouseY) {
+		return merger;
+	}
+
+}
Index: /trunk/src/org/expeditee/items/Text.java
===================================================================
--- /trunk/src/org/expeditee/items/Text.java	(revision 6)
+++ /trunk/src/org/expeditee/items/Text.java	(revision 6)
@@ -0,0 +1,1509 @@
+package org.expeditee.items;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.Stroke;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineBreakMeasurer;
+import java.awt.font.TextAttribute;
+import java.awt.font.TextHitInfo;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.text.AttributedString;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.expeditee.gui.FrameMouseActions;
+
+/**
+ * Represents text displayed on the screen, which may include multiple lines.
+ * All standard text properties can be set (font, style, size).
+ * 
+ * @author jdm18
+ * 
+ */
+public class Text extends Item {
+
+	public static final String FRAME_NAME_SEPARATOR = " on frame ";
+
+	/**
+	 * The default font used to display text items if no font is specified.
+	 */
+	public static final String DEFAULT_FONT = "Serif-Plain-18";
+
+	public static final Color DEFAULT_COLOR = Color.BLACK;
+
+	public static final int MINIMUM_RANGED_CHARS = 2;
+
+	public static final int LEFT_MARGIN = 13;
+
+	public static final int UP = 0;
+
+	public static final int DOWN = 1;
+
+	public static final int LEFT = 2;
+
+	public static final int RIGHT = 3;
+
+	public static final int HOME = 4;
+
+	public static final int END = 5;
+
+	private int _maxWidth = -1;
+
+	private int _justification = Item.JUSTIFICATION_NONE;
+
+	private int _spacing = -1;
+
+	private int _word_spacing = -1;
+
+	private int _initial_spacing = -1;
+
+	private int _letter_spacing = -1;
+
+	// used during ranging out
+	private int _selectionStart = -1;
+
+	private int _selectionEnd = -1;
+
+	// text is broken up into lines
+	private StringBuffer _text = new StringBuffer();
+
+	private List<TextLayout> _textLayouts = new LinkedList<TextLayout>();
+
+	private List<Integer> _lineOffsets = new LinkedList<Integer>();
+
+	private LineBreakMeasurer _lineBreaker = null;
+
+	// The font to display this text in
+	private Font _font;
+
+	// the rectangle surrounding this text item
+	private Polygon _poly = null;
+
+	/**
+	 * Creates a new Text Item with the given ID and text.
+	 * 
+	 * @param id
+	 *            The id of this item
+	 * @param text
+	 *            The text to use in this item
+	 */
+	public Text(int id, String text) {
+		super();
+		_text.append(text);
+		rebuild(false);
+
+		setID(id);
+	}
+
+	/**
+	 * Creates a new Text Item with the given ID
+	 * 
+	 * @param id
+	 *            The ID to of this item
+	 */
+	public Text(int id) {
+		super();
+		setID(id);
+	}
+
+	/**
+	 * Sets the maximum width of this Text item when justifcation is used.
+	 * passing in 0 or -1 means there is no maximum width
+	 * 
+	 * @param width
+	 *            The maximum width of this item when justification is applied
+	 *            to it.
+	 */
+	@Override
+	public void setWidth(int width) {
+		// 0 is the default
+		if (width == 0)
+			width = -1;
+
+		_maxWidth = width;
+	}
+
+	/**
+	 * Returns the maximum width of this Text item when justifcation is used.
+	 * Note the returned value may be -1, which indicates that there is no
+	 * maximum size set
+	 * 
+	 * @return The maximum width of this Text item when justification is used
+	 */
+	@Override
+	public int getWidth() {
+		return _maxWidth;
+	}
+
+	/**
+	 * Sets the justification of this Text item. The given integer should
+	 * correspond to one of the JUSTIFICATION constants defined in Item
+	 * 
+	 * @param just
+	 *            The justification to apply to this Text item
+	 */
+	public void setJustification(int just) {
+		_justification = just;
+		rejustify();
+	}
+
+	/**
+	 * Returns the current justification of this Text item, the default value is
+	 * Item.JUSTIFICATION_NONE
+	 * 
+	 * @return The justification of this Text item
+	 */
+	public int getJustification() {
+		return _justification;
+	}
+
+	private int getJustOffset(TextLayout layout) {
+		if (getWidth() < 0)
+			return 0;
+
+		if (getJustification() == Item.JUSTIFICATION_CENTER)
+			return (int) ((getWidth() - layout.getAdvance()) / 2);
+		else if (getJustification() == Item.JUSTIFICATION_RIGHT)
+			return (int) (getWidth() - layout.getAdvance());
+
+		return 0;
+	}
+
+	/**
+	 * Sets the text displayed on the screen to the given String
+	 * 
+	 * @param text
+	 *            The String to display on the screen when drawing this Item.
+	 */
+	public void setText(String text) {
+		_text = new StringBuffer(text);
+		rebuild(false);
+	}
+
+	public void setTextList(List<String> text) {
+		if (text == null || text.size() <= 0)
+			return;
+
+		setText(text.get(0));
+		for (int i = 1; i < text.size(); i++) {
+			appendText("\n");
+			appendText(text.get(i));
+		}
+		rebuild(false);
+	}
+
+	public void setAttributeValue(String value) {
+
+	}
+
+	/**
+	 * Inserts the given String at the start of the first line of this Text
+	 * Item.
+	 * 
+	 * @param text
+	 *            The String to insert.
+	 */
+	public void prependText(String text) {
+		_text.insert(0, text);
+		rebuild(false);
+	}
+
+	/**
+	 * If the first line of text starts with the given String, then it is
+	 * removed otherwise no action is taken.
+	 * 
+	 * @param text
+	 *            The String to remove from the first line of Text
+	 */
+	public void removeText(String text) {
+		if (_text.length() > 0 && _text.indexOf(text) == 0)
+			_text.delete(0, text.length());
+
+	}
+	
+	public void removeEndText(String textToRemove) {
+		int length = _text.length();
+		if (length > 0) {
+			int pos = _text.indexOf(textToRemove);
+			int textToRemoveLength = textToRemove.length();
+			if (pos + textToRemoveLength == length) {
+				_text.delete(pos, length);
+			}
+		}
+	}
+
+	/**
+	 * Appends the given String to any text already present in this Item
+	 * 
+	 * @param text
+	 *            The String to append.
+	 */
+	public void appendText(String text) {
+		_text.append(text);
+		rebuild(false);
+	}
+	
+	public void appendLine(String text) {
+		if (text == null)
+			text = "";
+		
+		if (_text.length() > 0)
+			_text.append('\n');
+			
+		_text.append(text);
+		rebuild(false);
+	}
+
+	/**
+	 * Tests if the first line of this Text starts with the given String.
+	 * 
+	 * @param text
+	 *            The prefix to check for
+	 * @return True if the first line starts with the given String, False
+	 *         otherwise.
+	 */
+	public boolean startsWith(String text) {
+		return startsWith(text, true);
+	}
+
+	public boolean startsWith(String text, boolean ignoreCase) {
+		if (text == null || _text == null || _text.length() < 1)
+			return false;
+
+		if (ignoreCase)
+			return _text.toString().toLowerCase()
+					.startsWith(text.toLowerCase());
+		else
+			return _text.indexOf(text) == 0;
+	}
+
+	/**
+	 * Inserts a character into the Text of this Item.
+	 * 
+	 * @param ch
+	 *            The character insert.
+	 * @param mouseX
+	 *            The X position to insert the Strings at.
+	 * @param mouseY
+	 *            The Y position to insert the Strings at.
+	 */
+	public Point insertChar(char ch, int mouseX, int mouseY) {
+		if (ch != '\t')
+			return insertText("" + ch, mouseX, mouseY);
+
+		return new Point(mouseX, mouseY);
+	}
+
+	public Point getEndLinePosition(int mouseY) {
+		return getEdgePosition(getLinePosition(mouseY), false);
+	}
+
+	public Point getStartLinePosition(int mouseY) {
+		return getEdgePosition(getLinePosition(mouseY), true);
+	}
+
+	public Point getEndParagraphPosition() {
+		return getEdgePosition(_textLayouts.size() - 1, false);
+	}
+
+	private Point getEdgePosition(int line, boolean start) {
+		// if there is no text yet, or the line is invalid
+		if (_text == null || _text.length() == 0 || line < 0
+				|| line > _textLayouts.size() - 1)
+			return new Point(getX(), getY());
+
+		TextLayout last = _textLayouts.get(line);
+		TextHitInfo hit;
+		if (start)
+			hit = last.getNextLeftHit(1);
+		else
+			hit = last.getNextRightHit(last.getCharacterCount() - 1);
+
+		// move the cursor to the new location
+		float[] caret = last.getCaretInfo(hit);
+		int y = getLineDrop(last) * line;
+
+		int x = ((int) (getX() + caret[0]) + getJustOffset(last));
+		x = Math
+				.min(
+						x,
+						(getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
+		return new Point(x, getY() + /* DisplayIO.getOffset() + */y
+				+ (int) caret[1]);
+	}
+
+	public void setSelectionStart(int mouseX, int mouseY) {
+		// determine what line is being pointed to
+		int line = getLinePosition(mouseY);
+
+		// get the character being pointed to
+		TextHitInfo hit = getCharPosition(line, mouseX);
+		_selectionStart = hit.getInsertionIndex() + _lineOffsets.get(line);
+	}
+
+	public void setSelectionEnd(int mouseX, int mouseY) {
+		// determine what line is being pointed to
+		int line = getLinePosition(mouseY);
+
+		// get the character being pointed to
+		TextHitInfo hit = getCharPosition(line, mouseX);
+		_selectionEnd = hit.getInsertionIndex() + _lineOffsets.get(line);
+	}
+
+	public void clearSelection() {
+		_selectionStart = -1;
+		_selectionEnd = -1;
+	}
+
+	public void clearSelectionEnd() {
+		_selectionEnd = -1;
+	}
+
+	public String copySelectedText() {
+		if (_selectionStart < 0 || _selectionEnd < 0)
+			return null;
+		else if (_selectionEnd > _text.length())
+			_selectionEnd = _text.length();
+
+		return _text.substring(Math.min(_selectionStart, _selectionEnd), Math
+				.max(_selectionStart, _selectionEnd));
+	}
+
+	public String cutSelectedText() {
+		if (_selectionStart < 0 || _selectionEnd < 0)
+			return null;
+		else if (_selectionEnd > _text.length())
+			_selectionEnd = _text.length();
+
+		int left = Math.min(_selectionStart, _selectionEnd);
+		int right = Math.max(_selectionStart, _selectionEnd);
+
+		String s = _text.substring(left, right);
+		for (int i = right; i > left; i--) {
+			_text.delete(i - 1, i);
+			if (_text.length() > 0) {
+				AttributedString inserting = new AttributedString(_text
+						.toString());
+				inserting.addAttribute(TextAttribute.FONT, getPaintFont());
+				_lineBreaker.deleteChar(inserting.getIterator(), i - 1);
+			}
+
+			rejustify();
+		}
+
+		return s;
+	}
+
+	public int getSelectedSize() {
+		if (_selectionEnd < 0 || _selectionStart < 0)
+			return 0;
+
+		//System.out.println(_selectionStart + ":" + _selectionEnd);
+
+		return Math.abs(_selectionEnd - _selectionStart);
+	}
+
+	/**
+	 * Inserts the given String into the Text at the position given by the
+	 * mouseX and mouseY coordinates
+	 * 
+	 * @param text
+	 *            The String to insert into this Text.
+	 * @param mouseX
+	 *            The X position to insert the String
+	 * @param mouseY
+	 *            The Y position to insert the String
+	 * @return The new location that the mouse cursor should be moved to
+	 */
+	public java.awt.Point insertText(String text, int mouseX, int mouseY) {
+		TextHitInfo hit;
+		TextLayout current = null;
+		int line;
+
+		// check for empty string
+		if (text == null || text.length() == 0)
+			return new Point(mouseX, mouseY);
+
+		// if there is no text yet
+		if (_text == null || _text.length() == 0) {
+			if (text.equals("@")) {
+
+			}
+
+			_text = new StringBuffer().append(text);
+			// create the linebreaker and layouts
+			rebuild(true);
+
+			current = _textLayouts.get(_textLayouts.size() - 1);
+			hit = current.getNextRightHit(0);
+			line = 0;
+
+			// otherwise, we are inserting text
+		} else {
+			// determine what line is being pointed to
+			line = getLinePosition(mouseY);
+
+			// get the character being pointed to
+			hit = getCharPosition(line, mouseX);
+
+			int pos = hit.getInsertionIndex() + _lineOffsets.get(line);
+
+			if (line > 0 && hit.getInsertionIndex() == 0)
+				pos++;
+
+			// compensate for newline character
+			// if(line > 0 && hit.getInsertionIndex() == 0)
+			// if(pos < (_text.length() - 1) && _text.charAt(pos) == '\n')
+			// pos++;
+
+			// if this is a backspace key
+			if (text.charAt(0) == KeyEvent.VK_BACK_SPACE) {
+				if (pos > 0) {
+					_text.delete(pos - 1, pos);
+
+					if (_text.length() > 0) {
+						AttributedString inserting = new AttributedString(_text
+								.toString());
+						inserting.addAttribute(TextAttribute.FONT,
+								getPaintFont());
+						_lineBreaker.deleteChar(inserting.getIterator(),
+								pos - 1);
+					}
+
+					pos--;
+				}
+				// if this is a delete key
+			} else if (text.charAt(0) == KeyEvent.VK_DELETE) {
+				if (pos < _text.length()) {
+					_text.delete(pos, pos + 1);
+
+					if (_text.length() > 0) {
+						AttributedString inserting = new AttributedString(_text
+								.toString());
+						inserting.addAttribute(TextAttribute.FONT,
+								getPaintFont());
+						_lineBreaker.deleteChar(inserting.getIterator(), pos);
+					}
+				}
+				// this is a normal insert
+			} else {
+				_text.insert(pos, text);
+				if (text.length() < 2) {
+					AttributedString inserting = new AttributedString(_text
+							.toString());
+					inserting.addAttribute(TextAttribute.FONT, getPaintFont());
+					_lineBreaker.insertChar(inserting.getIterator(), pos);
+				} else
+					_lineBreaker = null;
+
+				pos += text.length();
+			}
+
+			int newLine = line;
+
+			// if a rebuild is required
+			if (_lineBreaker == null)
+				rebuild(true);
+			else
+				rejustify();
+
+			// determine the new position the cursor should have
+			for (int i = 1; i < _lineOffsets.size(); i++) {
+				if (_lineOffsets.get(i) >= pos) {
+					newLine = i - 1;
+					break;
+				}
+			}
+
+			current = _textLayouts.get(newLine);
+			pos = pos - _lineOffsets.get(newLine);
+
+			if (newLine == line) {
+				if (pos > 0)
+					hit = current.getNextRightHit(pos - 1);
+				else
+					hit = current.getNextLeftHit(1);
+			} else if (newLine < line) {
+				hit = current.getNextRightHit(pos - 1);
+			} else {
+				hit = current.getNextRightHit(pos - 1);
+			}
+
+			line = newLine;
+		}
+
+		// move the cursor to the new location
+		float[] caret = current.getCaretInfo(hit);
+		int y = getLineDrop(current) * line;
+
+		int x = ((int) (getX() + caret[0]) + getJustOffset(current));
+		x = Math
+				.min(
+						x,
+						(getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
+		return new Point(x, getY() /* + DisplayIO.getOffset() */+ y
+				+ (int) caret[1]);
+	}
+
+	public Point moveCursor(int direction, int mouseX, int mouseY) {
+		// check for home or end keys
+		if (direction == HOME)
+			return getStartLinePosition(mouseY);
+
+		if (direction == END)
+			return getEndLinePosition(mouseY);
+
+		TextHitInfo hit;
+		TextLayout current;
+		int line;
+
+		// if there is no text yet
+		if (_text == null || _text.length() == 0) {
+			return new Point(mouseX, mouseY);
+			// otherwise, move the cursor
+		} else {
+			// determine the line of text to check
+			line = getLinePosition(mouseY);
+			if (line < 0)
+				line = _textLayouts.size() - 1;
+
+			// if the cursor is moving up or down, change the line
+			if (direction == UP)
+				line = Math.max(line - 1, 0);
+			else if (direction == DOWN)
+				line = Math.min(line + 1, _textLayouts.size() - 1);
+
+			hit = getCharPosition(line, mouseX);
+
+			if (direction == LEFT) {
+				if (hit.getInsertionIndex() > 0)
+					hit = _textLayouts.get(line).getNextLeftHit(hit);
+
+				if (line > 0
+						&& _text.charAt(hit.getInsertionIndex()
+								+ _lineOffsets.get(line)) == '\n') {
+					line--;
+					hit = _textLayouts.get(line).getNextRightHit(
+							_textLayouts.get(line).getCharacterCount() - 1);
+				}
+			} else if (direction == RIGHT) {
+				if (hit.getInsertionIndex() < _textLayouts.get(line)
+						.getCharacterCount()) {
+					hit = _textLayouts.get(line).getNextRightHit(hit);
+					while (hit.getCharIndex() > 0
+							&& hit.getCharIndex() < _textLayouts.get(line)
+									.getCharacterCount()
+							&& _text.charAt(_lineOffsets.get(line)
+									+ hit.getCharIndex() - 1) == '\t')
+						hit = _textLayouts.get(line).getNextRightHit(hit);
+				} else if (line < _textLayouts.size() - 1) {
+					line++;
+					hit = _textLayouts.get(line).getNextLeftHit(1);
+				}
+			}
+
+			current = _textLayouts.get(line);
+		}
+
+		// move the cursor to the new location
+		float[] caret = current.getCaretInfo(hit);
+		int y = getLineDrop(current) * line;
+
+		return new Point((int) (getX() + caret[0]) + getJustOffset(current),
+				getY() /* + DisplayIO.getOffset() */+ y + (int) caret[1]);
+	}
+
+	/**
+	 * Iterates through the given line string and returns the position of the
+	 * character being pointed at by the mouse.
+	 * 
+	 * @param line
+	 *            The index of the _text array of the String to be searched.
+	 * @param mouseX
+	 *            The X coordinate of the mouse
+	 * @return The position in the string of the character being pointed at.
+	 */
+	private TextHitInfo getCharPosition(int line, int mouseX) {
+		if (line < 0 || line >= _textLayouts.size())
+			return null;
+
+		TextLayout layout = _textLayouts.get(line);
+		mouseX += getOffset().x;
+		mouseX -= getJustOffset(layout);
+
+		return layout.hitTestChar(mouseX - getX(), 0);
+	}
+
+	private int getLinePosition(int mouseY) {
+		mouseY += getOffset().y;
+
+		int y = getY();
+
+		for (TextLayout text : _textLayouts) {
+			// calculate X to ensure it is in the shape
+			Rectangle bounds = text.getLogicalHighlightShape(0,
+					text.getCharacterCount()).getBounds();
+
+			if (bounds.getWidth() < 1)
+				bounds = new Rectangle((int) bounds.getMinX(), (int) bounds
+						.getMinY(), 10, (int) bounds.getHeight());
+
+			double x = bounds.getCenterX();
+
+			// if(text.getLogicalHighlightShape(0,
+			// text.getCharacterCount()).contains(text.getBounds().getMinX()
+			// + 10, mouseY - getY() - (y - getY())))
+			if (bounds.contains(x, mouseY - getY() - (y - getY())))
+				return _textLayouts.indexOf(text);
+
+			// check if the cursor is between lines
+			if (mouseY - getY() - (y - getY()) < bounds.getMinY())
+				return Math.max(0, _textLayouts.indexOf(text) - 1);
+
+			y += getLineDrop(text);
+		}
+
+		return _textLayouts.size() - 1;
+	}
+
+	/**
+	 * Sets the Font that this text will be displayed with on the screen.
+	 * 
+	 * @param font
+	 *            The Font to display the Text of this Item in.
+	 */
+	public void setFont(Font font) {
+		// all decoding occurs in the Utils class
+		_font = font;
+
+		// rejustify();
+		rebuild(false);
+	}
+
+	/**
+	 * Returns the Font that this Text is currently using when painting to the
+	 * screen
+	 * 
+	 * @return The Font used to display this Text on the screen.
+	 */
+	public Font getFont() {
+		return _font;
+	}
+
+	public Font getPaintFont() {
+		if (getFont() == null)
+			return Font.decode(DEFAULT_FONT);
+
+		return getFont();
+	}
+
+	public String getFamily() {
+		return getPaintFont().getFamily();
+	}
+
+	public void setFamily(String newFamily) {
+		String toDecode = newFamily + "-" + getFontStyle() + "-" + getSize();
+		setFont(Font.decode(toDecode));
+	}
+
+	public String getFontStyle() {
+		Font f = getPaintFont();
+		String s = "";
+		
+		if (f.isPlain())
+			s += "Plain";
+
+		if (f.isBold())
+			s += "Bold";
+
+		if (f.isItalic())
+			s += "Italic";
+
+		return s;
+	}
+	
+	public static final String[] FONT_WHEEL = { "sansserif", "monospaced", "serif", "dialog", "dialoginput" };
+	public static final char[] FONT_CHARS = { 's', 'm', 't', 'd', 'i' };
+	
+	public void toggleFontFamily() {
+		String fontFamily = getFamily().toLowerCase();
+		//set it to the first font by default
+		setFamily(FONT_WHEEL[0]);
+		
+		for (int i = 0; i < FONT_WHEEL.length - 3; i++) {
+			if(fontFamily.equals(FONT_WHEEL[i])){
+				setFamily(FONT_WHEEL[i+1]);
+				break;
+			}
+		}
+	}
+	
+	public void toggleFontStyle() {
+		Font currentFont = getPaintFont();
+		if (currentFont.isPlain())
+			setFont(currentFont.deriveFont(Font.BOLD));
+		else if (currentFont.isBold() && currentFont.isItalic())
+			setFont(currentFont.deriveFont(Font.PLAIN));
+		else if (currentFont.isBold())
+			setFont(currentFont.deriveFont(Font.ITALIC));
+		else
+			setFont(currentFont.deriveFont(Font.ITALIC + Font.BOLD));
+	}
+
+	public void setFontStyle(String newFace) {
+		if (newFace == null || newFace.trim().length() == 0) {
+			setFont(getPaintFont().deriveFont(Font.PLAIN));
+			return;
+		}
+
+		newFace = newFace.toLowerCase().trim();
+
+		if (newFace.equals("plain")) {
+			setFont(getPaintFont().deriveFont(Font.PLAIN));
+		} else if (newFace.equals("bold")) {
+			setFont(getPaintFont().deriveFont(Font.BOLD));
+		} else if (newFace.equals("italic")) {
+			setFont(getPaintFont().deriveFont(Font.ITALIC));
+		} else if (newFace.contains("bold") && newFace.contains("italic")) {
+			setFont(getPaintFont().deriveFont(Font.BOLD + Font.ITALIC));
+		}
+
+	}
+
+	/**
+	 * Returns a String array of this Text object's text, split up into separate
+	 * lines.
+	 * 
+	 * @return The String array with one element per line of text in this Item.
+	 */
+	public List<String> getText() {
+		if (_text == null)
+			return null;
+		try {
+			List<String> list = new LinkedList<String>();
+
+			int last = 0;
+			for (int offset : _lineOffsets) {
+				if (offset != last) {
+					list
+							.add(_text.substring(last, offset).replaceAll("\n",
+									""));
+				}
+				last = offset;
+			}
+
+			return list;
+		} catch (Exception e) {
+			System.out.println(e.getMessage());
+			return null;
+		}
+	}
+
+	public String getTextNoList() {
+		return _text.toString();
+	}
+
+	/**
+	 * Returns the first line of text in this Text Item
+	 * 
+	 * @return The first line of Text
+	 */
+	public String getFirstLine() {
+		if (_text == null || _text.length() == 0)
+			return null;
+
+		if (_text.indexOf("\n") < 0)
+			return _text.toString();
+
+		return _text.substring(0, _text.indexOf("\n"));
+	}
+
+	/**
+	 * Sets the inter-line spacing (in pixels) of this text.
+	 * 
+	 * @param spacing
+	 *            The number of pixels to allow between each line
+	 */
+	public void setSpacing(int spacing) {
+		_spacing = spacing;
+		updatePolygon();
+	}
+
+	/**
+	 * Returns the inter-line spacing (in pixels) of this Text.
+	 * 
+	 * @return The spacing (inter-line) in pixels of this Text.
+	 */
+	public int getSpacing() {
+		return _spacing;
+	}
+
+	private int getLineDrop(TextLayout layout) {
+		if (getSpacing() < 0)
+			return (int) (layout.getAscent() + layout.getDescent() + layout
+					.getLeading());
+
+		return (int) (layout.getAscent() + layout.getDescent() + getSpacing());
+	}
+
+	public void setWordSpacing(int spacing) {
+		_word_spacing = spacing;
+	}
+
+	public int getWordSpacing() {
+		return _word_spacing;
+	}
+
+	public void setLetterSpacing(int spacing) {
+		_letter_spacing = spacing;
+	}
+
+	public int getLetterSpacing() {
+		return _letter_spacing;
+	}
+
+	public void setInitialSpacing(int spacing) {
+		_initial_spacing = spacing;
+	}
+
+	public int getInitialSpacing() {
+		return _initial_spacing;
+	}
+
+	@Override
+	public boolean contains(int mouseX, int mouseY) {
+		mouseX += getOffset().x;
+		mouseY += getOffset().y;
+
+		int textY = getY();
+		int textX = getX();
+		int gravity = getGravity() * 2;
+
+		Rectangle outline = getPolygon().getBounds();
+
+		// Check if its outside the top and left and bottom bounds
+		if (outline.x - mouseX > gravity || outline.y - mouseY > gravity
+				|| mouseY - (outline.y + outline.height) > gravity
+				|| mouseX - (outline.x + outline.width) > gravity) {
+			return false;
+		}
+
+		// check if the cursor is on the left border of the bounding box
+		if (Math.abs(mouseX - LEFT_MARGIN - outline.x) <= gravity) {
+			return true;
+		}
+
+		for (TextLayout text : _textLayouts) {
+			// check left and right of each box
+			Rectangle textOutline = text.getLogicalHighlightShape(0,
+					text.getCharacterCount()).getBounds();
+
+			// check if the cursor is within the top, bottom and within the
+			// gravity of right
+			if (mouseY - textY > textOutline.y
+					&& mouseY - textY < textOutline.y + textOutline.height
+					&& mouseX - textX < textOutline.width + gravity)
+				return true;
+			textY += getLineDrop(text);
+		}
+
+		return false;
+	}
+
+	/**
+	 * Updates the Polygon (rectangle) that surrounds this Text on the screen.
+	 */
+	protected void updatePolygon() {
+		// if there is no text, there is nothing to do
+		if (_text == null)
+			return;
+
+		_poly = new Polygon();
+
+		if (_textLayouts.size() < 1)
+			return;
+
+		int minX = Integer.MAX_VALUE;
+		int maxX = Integer.MIN_VALUE;
+
+		int minY = Integer.MAX_VALUE;
+		int maxY = Integer.MIN_VALUE;
+
+		float y = -1;
+
+		synchronized (_textLayouts) {
+			for (TextLayout layout : _textLayouts) {
+				Rectangle2D bounds = layout.getLogicalHighlightShape(0,
+						layout.getCharacterCount()).getBounds2D();
+
+				if (y < 0)
+					y = 0;
+				else
+					y += getLineDrop(layout);
+
+				maxX = Math.max(maxX, (int) bounds.getMaxX());
+				minX = Math.min(minX, (int) bounds.getMinX());
+				maxY = Math.max(maxY, (int) (bounds.getMaxY() + y));
+				minY = Math.min(minY, (int) (bounds.getMinY() + y));
+			}
+		}
+
+		minX -= Item.MARGIN_RIGHT;
+
+		if (getWidth() > 0)
+			maxX = getWidth();
+
+		_poly.addPoint(minX - getGravity(), minY - getGravity());
+		_poly.addPoint(maxX + getGravity(), minY - getGravity());
+		_poly.addPoint(maxX + getGravity(), maxY + getGravity());
+		_poly.addPoint(minX - getGravity(), maxY + getGravity());
+
+		// _poly.addPoint(minX, minY);
+		// _poly.addPoint(maxX, minY);
+		// _poly.addPoint(maxX, maxY);
+		// _poly.addPoint(minX, maxY);
+	}
+
+	// TODO it seems like this method has some exponencial processing which
+	// makes items copy really slowly when there are lots of lines of text!
+	// This needs to be fixed!!
+	private void rebuild(boolean limitWidth) {
+		// if there is no text, there is nothing to do
+		if (_text == null || _text.length() == 0)
+			return;
+
+		AttributedString paragraphText = new AttributedString(_text.toString());
+		paragraphText.addAttribute(TextAttribute.FONT, getPaintFont());
+		_lineBreaker = new LineBreakMeasurer(paragraphText.getIterator(),
+				new FontRenderContext(null, true, true));
+
+		float width = Float.MAX_VALUE;
+
+		if (limitWidth) {
+			if (getWidth() > 0)
+				width = getWidth();
+			else if (getMaxSize() != null)
+				width = Math.max(50, getMaxSize().width - getX()
+						- Item.MARGIN_RIGHT);
+		}
+
+		_textLayouts.clear();
+		_lineOffsets.clear();
+		// the first line always has a 0 offset
+		_lineOffsets.add(0);
+
+		TextLayout layout;
+		_lineBreaker.setPosition(0);
+
+		// --- Get the output of the LineBreakMeasurer and store it in a
+		while ((layout = _lineBreaker.nextLayout(width)) != null) {
+			// for some reason lineBreaker will not break on newline
+			// characters
+			// so they have to be check manually
+			int start = _lineOffsets.get(_lineOffsets.size() - 1);
+
+			// check through the current line for newline characters
+			for (int i = start + 1; i < _text.length(); i++) {
+				if (_text.charAt(i) == '\n') {// || c == '\t'){
+					_lineBreaker.setPosition(start);
+					layout = _lineBreaker.nextLayout(width, i, false);
+					break;
+				}
+			}
+
+			_lineOffsets.add(_lineBreaker.getPosition());
+
+			if (getWidth() > 0 && getJustification() == Item.JUSTIFICATION_FULL
+					&& _lineBreaker.getPosition() < _text.length())
+				layout = layout.getJustifiedLayout(width);
+
+			_textLayouts.add(layout);
+		}
+
+		updatePolygon();
+	}
+
+	private void rejustify() {
+		// if there is no text, there is nothing to do
+		if (_text == null || _text.length() == 0)
+			return;
+
+		// only recreate linebreaker if necessary
+		if (_lineBreaker == null) {
+			rebuild(true);
+			return;
+
+			/*
+			 * AttributedString paragraphText = new
+			 * AttributedString(_text.toString());
+			 * paragraphText.addAttribute(TextAttribute.FONT, getPaintFont());
+			 * _lineBreaker = new LineBreakMeasurer(paragraphText.getIterator(),
+			 * new FontRenderContext(null, true, true));
+			 */
+		}
+
+		float width = Float.MAX_VALUE;
+		if (getWidth() > 0)
+			width = getWidth();
+		else if (getMaxSize() != null)
+			width = Math.max(50, getMaxSize().width - getX()
+					- Item.MARGIN_RIGHT);
+
+		_textLayouts.clear();
+		_lineOffsets.clear();
+		// the first line always has a 0 offset
+		_lineOffsets.add(0);
+
+		TextLayout layout;
+		_lineBreaker.setPosition(0);
+
+		// --- Get the output of the LineBreakMeasurer and store it in a
+		while ((layout = _lineBreaker.nextLayout(width)) != null) {
+			// for some reason lineBreaker will not break on newline
+			// characters
+			// so they have to be check manually
+			int start = _lineOffsets.get(_lineOffsets.size() - 1);
+
+			// check through the current line for newline characters
+			for (int i = start + 1; i < _text.length(); i++) {
+				if (_text.charAt(i) == '\n') {// || c == '\t'){
+					_lineBreaker.setPosition(start);
+					layout = _lineBreaker.nextLayout(width, i, false);
+					break;
+				}
+			}
+
+			_lineOffsets.add(_lineBreaker.getPosition());
+
+			if (getWidth() > 0 && getJustification() == Item.JUSTIFICATION_FULL
+					&& _lineBreaker.getPosition() < _text.length())
+				layout = layout.getJustifiedLayout(width);
+
+			_textLayouts.add(layout);
+		}
+
+		updatePolygon();
+	}
+
+	private int _alpha = -1;
+
+	public void setAlpha(int alpha) {
+		_alpha = alpha;
+	}
+
+	private Point getSelectedRange(int line) {
+		if (_selectionEnd >= _text.length()) {
+			_selectionEnd = _text.length();
+		}
+
+		if (_selectionStart < 0)
+			_selectionStart = 0;
+
+		if (_selectionStart < 0 || _selectionEnd < 0)
+			return null;
+
+		int selectionLeft = Math.min(_selectionStart, _selectionEnd);
+		int selectionRight = Math.max(_selectionStart, _selectionEnd);
+
+		// if the selection is after this line, return null
+		if (_lineOffsets.get(line) > selectionRight)
+			return null;
+
+		// if the selection is before this line, return null
+		if (_lineOffsets.get(line) < selectionLeft
+				&& _lineOffsets.get(line)
+						+ _textLayouts.get(line).getCharacterCount() < selectionLeft)
+			return null;
+
+		// Dont highlight a single char
+		// if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
+		// return null;
+
+		// the selection occurs on this line, determine where it lies on the
+		// line
+		int start = Math.max(0, selectionLeft - _lineOffsets.get(line));
+		// int end = Math.min(_lineOffsets.get(line) +
+		// _textLayouts.get(line).getCharacterCount(), _selectionEnd);
+		int end = Math.min(selectionRight - _lineOffsets.get(line),
+				+_textLayouts.get(line).getCharacterCount());
+
+		// System.out.println(line + ": " + start + "x" + end + " (" +
+		// _selectionStart + "x" + _selectionEnd + ")");
+		return new Point(start, end);
+	}
+
+	@Override
+	public void paint(Graphics2D g) {
+		// if there is no text to paint, do nothing.
+		if (_text == null || _text.length() == 0 || getMaxSize() == null)// ||
+			// _textLayouts.size()
+			// < 1)
+			return;
+
+		if (_textLayouts.size() < 1) {
+			rebuild(true);
+			System.out.println("Error: " + _text);
+			return;
+		}
+
+		// the background is only cleared if required
+		if (getBackgroundColor() != null) {
+			g.setColor(getBackgroundColor());
+			g.fill(getPolygon());
+		}
+
+		float y = getY();
+		Color c = getPaintColor();
+		if (_alpha > 0)
+			c = new Color(c.getRed(), c.getGreen(), c.getBlue(), _alpha);
+
+		g.setColor(c);
+
+		Color selection;
+
+		/*
+		 * Color main = getPaintColor(); Color back = getPaintBackgroundColor();
+		 * 
+		 * if (Math.abs(main.getRed() - back.getRed()) < 10 &&
+		 * Math.abs(main.getGreen() - back.getGreen()) < 10 &&
+		 * Math.abs(main.getBlue() - back.getBlue()) < 10) { selection = new
+		 * Color(Math.abs(255 - main.getRed()), Math .abs(255 -
+		 * main.getGreen()), Math.abs(255 - main.getBlue())); } else { selection =
+		 * new Color((main.getRed() + (back.getRed() * 2)) / 3, (main.getGreen() +
+		 * (back.getGreen() * 2)) / 3, (main .getBlue() + (back.getBlue() * 2)) /
+		 * 3); }
+		 */
+		int green = 160;
+		int red = 160;
+		int blue = 160;
+		if (FrameMouseActions.wasDeleteClicked()) {
+			green = 235;
+			red = 235;
+			blue = 140;
+		} else if (FrameMouseActions.getLastMouseButton() == MouseEvent.BUTTON1) {
+			red = 255;
+		} else if (FrameMouseActions.getLastMouseButton() == MouseEvent.BUTTON2) {
+			green = 255;
+		} else if (FrameMouseActions.getLastMouseButton() == MouseEvent.BUTTON3) {
+			blue = 255;
+		}
+		selection = new Color(red, green, blue);
+
+		// width -= getX();
+		// int line = 0;
+		// boolean tab = false;
+		synchronized (_textLayouts) {
+			for (int i = 0; i < _textLayouts.size(); i++) {
+				TextLayout layout = _textLayouts.get(i);
+
+				Point p = getSelectedRange(i);
+				if (p != null) {
+					AffineTransform at = new AffineTransform();
+					AffineTransform orig = g.getTransform();
+					at.translate(getX() + getJustOffset(layout), y);
+					g.setTransform(at);
+
+					g.setColor(selection);
+					g.fill(layout.getLogicalHighlightShape(p.x, p.y));
+
+					g.setTransform(orig);
+					g.setColor(c);
+				}
+
+				layout.draw(g, getX() + getJustOffset(layout), y);
+
+				/*
+				 * AffineTransform at = new AffineTransform(); AffineTransform
+				 * orig = g.getTransform(); at.translate(getX() +
+				 * getJustOffset(layout), y); g.setTransform(at);
+				 * g.draw(layout.getLogicalHighlightShape(0,
+				 * layout.getCharacterCount())); g.setTransform(orig); /*
+				 * if(_text.charAt(_lineOffsets.get(line) +
+				 * (layout.getCharacterCount() - 1)) == '\t'){ tab = true; x =
+				 * (int) (getX() + x + 20 + layout.getVisibleAdvance()); }else{
+				 */
+				y += getLineDrop(layout);
+				/*
+				 * tab = false; }
+				 * 
+				 * line++;
+				 */
+			}
+		}
+
+		if (getLink() != null || getAction() != null) {
+			if (getLink() != null && getAction() != null) {
+				g.setColor(LINK_ACTION_COLOR);
+			} else if (getLink() != null) {
+				g.setColor(LINK_COLOR);
+			} else if (getAction() != null) {
+				g.setColor(ACTION_COLOR);
+			}
+
+			AffineTransform at = new AffineTransform();
+			AffineTransform orig = g.getTransform();
+			at.translate(getX() - LEFT_MARGIN, getY()
+					- (_textLayouts.get(0).getAscent() / 2));
+			g.setTransform(at);
+
+			if (getLinkMark() && getLink() != null) {
+				g.drawPolygon(getCircle());
+
+				// if the link is not valid, cross out the circle
+				if (!isLinkValid())
+					g.drawPolygon(getCircleCross());
+			}
+
+			if (getActionMark() && getAction() != null) {
+				g.drawPolygon(getCircle());
+				g.fillPolygon(getCircle());
+
+				// if the link is not valid, cross out the circle
+				if (!isLinkValid() && getLink() != null) {
+					g.setColor(getParent().getPaintBackgroundColor());
+					g.drawPolygon(getCircleCross());
+				}
+			}
+
+			// reset the graphics tranformation
+			g.setTransform(orig);
+		}
+
+		if (isHighlighted()) {
+			g.setColor(getPaintHighlightColor());
+			Stroke _highlightStroke = new BasicStroke(
+					(float) _highlightThickness, BasicStroke.CAP_BUTT,
+					BasicStroke.JOIN_ROUND);
+			g.setStroke(_highlightStroke);
+			g.drawPolygon(getPolygon());
+		}
+	}
+
+	@Override
+	public int showHighlight(boolean val) {
+		super.showHighlight(val);
+
+		if (val)
+			return Item.UNCHANGED_CURSOR;
+		else
+			return Item.DEFAULT_CURSOR;
+	}
+
+	/**
+	 * Determines if this text has any text in it.
+	 * 
+	 * @return True if this Item has no text in it, false otherwise.
+	 */
+	public boolean isEmpty() {
+		if (_text == null || _text.length() == 0)
+			return true;
+
+		return false;
+	}
+
+	@Override
+	public Polygon getPolygon() {
+		updatePolygon();
+
+		Polygon external = new Polygon(_poly.xpoints, _poly.ypoints,
+				_poly.npoints);
+		external.translate(getX(), getY());
+
+		return external;
+	}
+
+	@Override
+	public Text copy() {
+		Text copy = new Text(getID());
+
+		// copy standard item values
+		Item.DuplicateItem(this, copy);
+
+		// copy values specific to text items
+		copy.setSpacing(getSpacing());
+		copy.setInitialSpacing(getInitialSpacing());
+		copy.setJustification(getJustification());
+		copy.setLetterSpacing(getLetterSpacing());
+		copy.setWordSpacing(getWordSpacing());
+		copy.setWidth(getWidth());
+		copy.setFont(getFont());
+		copy.setText(_text.toString());
+
+		return copy;
+	}
+
+	@Override
+	public int getSize() {
+		return getPaintFont().getSize();
+	}
+
+	/**
+	 * Returns the number of characters in this Text, excluding new lines.
+	 * 
+	 * @return The sum of the length of each line of text
+	 */
+	public int getLength() {
+		return _text.length();
+	}
+
+	@Override
+	public void setSize(int size) {
+		setFont(getPaintFont().deriveFont((float) size));
+	}
+
+	@Override
+	public void setAnnotation(boolean val) {
+		if (val) {
+			// if this is already an annotation, do nothing
+			if (isAnnotation())
+				return;
+
+			//Remove 'a: ' from the begining if it is there
+			if (_text.length() > 3 && _text.charAt(0) == 'a' && _text.charAt(1) == ':'){
+				_text.delete(0, 2);
+				while (_text.charAt(0) == ' ')
+					_text.delete(0, 1);
+			}
+			_text.insert(0, "@");
+
+			if (_lineBreaker != null) {
+				AttributedString inserting = new AttributedString(_text
+						.toString());
+				inserting.addAttribute(TextAttribute.FONT, getPaintFont());
+				_lineBreaker.insertChar(inserting.getIterator(), 0);
+			}
+		} else {
+			// if this is not an annotation, do nothing
+			if (!isAnnotation())
+				return;
+
+			_text.deleteCharAt(0);
+
+			if (_lineBreaker != null) {
+				AttributedString inserting = new AttributedString(_text
+						.toString());
+				inserting.addAttribute(TextAttribute.FONT, getPaintFont());
+				_lineBreaker.deleteChar(inserting.getIterator(), 0);
+			}
+		}
+		rebuild(false);
+	}
+
+	@Override
+	public boolean isAnnotation() {
+		if (_text != null && _text.length() > 0 && _text.indexOf("@") == 0)
+			return true;
+
+		return false;
+	}
+
+	public boolean isSpecialAnnotation() {
+		assert _text != null;
+		String s = _text.toString().toLowerCase();
+		if (s.length() > 0 && s.indexOf("@") == 0) {
+			if (s.equals("@old") || s.equals("@ao")
+					|| s.equals("@itemtemplate") || s.equals("@parent")
+					|| s.equals("@more") || s.equals("@next")
+					|| s.equals("@previous") || s.equals("@i")
+					|| s.equals("@f"))
+				return true;
+		}
+
+		return false;
+	}
+
+	@Override
+	public Item merge(Item merger, int mouseX, int mouseY) {
+		if (merger instanceof Dot) {
+			Dot d = (Dot) merger;
+			List<Line> lines = new LinkedList<Line>();
+			lines.addAll(d.getLines());
+			for (Line line : lines)
+				line.replaceEnd(d, this);
+
+			d.removeAllLines();
+			return null;
+		}
+
+		if (!(merger instanceof Text))
+			return merger;
+
+		Text merge = (Text) merger;
+
+		// insertText(merge.getText(), mouseX, mouseY);
+
+		// if the item being merged has a link
+		if (merge.getLink() != null) {
+			// if this item has a link, keep it on the cursor
+			if (getLink() != null) {
+				merge.setText(merge.getLink());
+				merge.setLink(null);
+				// return merge;
+				// TODO get this to return the merged item and attach it to the
+				// cursor only when the user presses the middle button.
+			} else
+				setLink(merge.getLink());
+		}
+
+		return null;
+	}
+
+	/**
+	 * Resets the position of the item to the default position for a title item.
+	 * 
+	 */
+	public void resetTitlePosition() {
+		setPosition(org.expeditee.io.Conversion.X_ADJUST, getSize());
+	}
+
+	public String stripFirstWord() {
+		int firstSpace = _text.toString().indexOf(' ');
+
+		// if there is only one word just make it blank
+		if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
+			String text = _text.toString();
+			setText("");
+			return text;
+		}
+
+		String firstWord = _text.toString().substring(0, firstSpace);
+
+		String text = _text.toString();
+		int secondWord;
+		for (secondWord = firstSpace; secondWord < _text.length(); secondWord++) {
+			if (!Character.isSpaceChar(text.charAt(secondWord))) {
+				break;
+			}
+		}
+		setText(_text.toString().substring(secondWord));
+
+		return firstWord;
+	}
+
+	public String toString() {
+		String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
+
+		if (getParent() != null)
+			return message + getParent().getFrameName();
+		return message + getDateCreated();
+	}
+
+	public Text getTemplateForm() {
+		Text template = this.copy();
+		template.setID(-1);
+		//The template must have text otherwise the bounds height will be zero!!
+		//This will stop escape drop down from working if there is no item template
+		template.setText("@");
+		return template;
+	}
+}
Index: /trunk/src/org/expeditee/simple/BelowMinParametreCountException.java
===================================================================
--- /trunk/src/org/expeditee/simple/BelowMinParametreCountException.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/BelowMinParametreCountException.java	(revision 6)
@@ -0,0 +1,9 @@
+package org.expeditee.simple;
+
+public class BelowMinParametreCountException extends RuntimeException {
+	static final long serialVersionUID = -7034897190745766939L;
+
+	public BelowMinParametreCountException(int paramCount) {
+		super("Expected at least " + paramCount + " params");
+	}
+}
Index: /trunk/src/org/expeditee/simple/Context.java
===================================================================
--- /trunk/src/org/expeditee/simple/Context.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/Context.java	(revision 6)
@@ -0,0 +1,146 @@
+package org.expeditee.simple;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+
+public class Context {
+	private Primitives primitives_ = new Primitives();
+
+	private Pointers pointers_ = new Pointers();
+
+	public Primitives getPrimitives() {
+		return primitives_;
+	}
+
+	public Pointers getPointers() {
+		return pointers_;
+	}
+
+	public Context() {
+	}
+
+	public int size() {
+		return primitives_.size() + pointers_.size();
+	}
+
+	public void clear() {
+		primitives_.clear();
+		pointers_.clear();
+	}
+
+	public void display() {
+		if (size() == 0) {
+			FrameGraphics.ErrorMessage("No variables exist!");
+			return;
+		}
+
+		primitives_.display();
+		pointers_.display();
+	}
+
+	/**
+	 * 
+	 * @param var1
+	 * @param var2
+	 * @return
+	 * @throws Exception
+	 *             if the variables could not be converted to the same type.
+	 */
+	public boolean equalValues(String var1, String var2) throws Exception {
+		if (Primitives.isPrimitive(var1) && Primitives.isPrimitive(var2)) {
+			return primitives_.equalValues(var1, var2);
+		} else if (Pointers.isPointer(var1) && Pointers.isPointer(var2)) {
+			throw new UnsupportedOperationException(
+					"Pointer comparison not yet implemented");
+		}
+		return false;
+	}
+
+	public void readFrame(String frameNameVar, String frameVar,
+			String successVar) throws Exception {
+		// Get the values to be set
+		String frameName = primitives_.getStringValue(frameNameVar);
+		Frame currentFrame = FrameIO.LoadFrame(frameName);
+
+		pointers_.setObject(frameVar, currentFrame);
+
+		if (successVar != null) {
+			Boolean success = currentFrame != null;
+			primitives_.setValue(successVar, new SBoolean(success));
+		}
+	}
+
+	public void createFrame(String framesetNameVar, String frameVar,
+			String successVar) throws Exception {
+		// Get the values to be set
+		String framesetName = primitives_.getStringValue(framesetNameVar);
+		Frame currentFrame = FrameIO.CreateFrame(framesetName, "", null);
+		pointers_.setObject(frameVar, currentFrame);
+
+		if (successVar != null) {
+			Boolean success = currentFrame != null;
+			primitives_.setValue(successVar, new SBoolean(success));
+		}
+	}
+
+	public void closeFrame(String frameVar, String successVar) throws Exception {
+		// Get the values to be set
+		Frame frame = (Frame) pointers_.getVariable(frameVar).getValue();
+		String contents = FrameIO.SaveFrame(frame, false);
+
+		if (successVar != null) {
+			Boolean success = contents != null;
+			primitives_.setValue(successVar, new SBoolean(success));
+		}
+	}
+
+	public void closeWriteFile(String fileVar) throws Exception {
+		BufferedWriter bw = (BufferedWriter) pointers_.getVariable(fileVar)
+				.getValue();
+		bw.close();
+	}
+
+	public void writeFile(String fileVar, String text) throws Exception {
+		BufferedWriter bw = (BufferedWriter) pointers_.getVariable(fileVar)
+				.getValue();
+		bw.write(text);
+	}
+
+	public void openWriteFile(String fileNameVar, String fileVar)
+			throws Exception {
+		openWriteFile(fileNameVar, fileVar, null);
+	}
+
+	public void openWriteFile(String fileNameVar, String fileVar,
+			String successVar) throws Exception {
+		// Get the values to be set
+		File filePath = new File(primitives_.getStringValue(fileNameVar));
+		BufferedWriter currentFile = new BufferedWriter(
+				new FileWriter(filePath));
+
+		pointers_.setObject(fileVar, currentFile);
+
+		if (successVar != null) {
+			Boolean success = currentFile != null;
+			primitives_.setValue(successVar, new SBoolean(success));
+		}
+	}
+
+	public boolean isDefined(String varName) {
+		try {
+			if (Primitives.isPrimitive(varName)) {
+				getPrimitives().getVariable(varName);
+			} else {
+				getPointers().getVariable(varName);
+			}
+			return true;
+		} catch (Exception e) {
+			return false;
+		}
+	}
+}
Index: /trunk/src/org/expeditee/simple/IncorrectParametreCountException.java
===================================================================
--- /trunk/src/org/expeditee/simple/IncorrectParametreCountException.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/IncorrectParametreCountException.java	(revision 6)
@@ -0,0 +1,9 @@
+package org.expeditee.simple;
+
+public class IncorrectParametreCountException extends RuntimeException {
+	static final long serialVersionUID = -7034897190745766939L;
+
+	public IncorrectParametreCountException(int paramCount) {
+		super("Expected exactly " + paramCount + " params");
+	}
+}
Index: /trunk/src/org/expeditee/simple/IncorrectTypeException.java
===================================================================
--- /trunk/src/org/expeditee/simple/IncorrectTypeException.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/IncorrectTypeException.java	(revision 6)
@@ -0,0 +1,13 @@
+package org.expeditee.simple;
+
+public class IncorrectTypeException extends RuntimeException {
+	static final long serialVersionUID = -7034897190745766939L;
+
+	public IncorrectTypeException(String type, int no) {
+		super("Expected param " + no + " to be " + type);
+	}
+
+	public IncorrectTypeException(String var, String type) {
+		super("Expected param " + var + " to be " + type);
+	}
+}
Index: /trunk/src/org/expeditee/simple/Pointers.java
===================================================================
--- /trunk/src/org/expeditee/simple/Pointers.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/Pointers.java	(revision 6)
@@ -0,0 +1,59 @@
+package org.expeditee.simple;
+
+public class Pointers extends Variables<SPointer<?>> {
+
+	private static final String[] prefixes = new String[] {
+			SPointer.itemPrefix, SPointer.framePrefix, SPointer.filePrefix };
+
+	public static boolean isPointer(String varName) {
+		for (String s : prefixes) {
+			if (varName.startsWith(s))
+				return true;
+		}
+		return false;
+	}
+
+	public Pointers() {
+		super();
+
+	}
+
+	protected String getType() {
+		return "pointer";
+	}
+
+	// TODO how do I put these two together without the warning
+	public void add(String name, SPointer<?> value) {
+		addT(name, value);
+	}
+
+	public <T> void addT(String name, SPointer<T> value) {
+		list_.add(new SPointer<T>(name, value.getValue()));
+	}
+
+	/**
+	 * Assigns a new value to a pointer variable in the list. If the variable
+	 * does not already exist the variable is created.
+	 * 
+	 * @param <T>
+	 * @param name
+	 *            the name of the variable which will have its value changed.
+	 * @param value
+	 *            the new value of the variable.
+	 * @throws Exception
+	 *             if an error occurs in changing the variables value.
+	 */
+	public <T> void setObject(String name, T value) throws Exception {
+		SPointer v = null;
+		try {
+			// if it is an existing variable change the value
+			v = getVariable(name);
+		} catch (Exception e) {
+			// If the first variable doesnt exist then add it
+			list_.add(new SPointer<T>(name, value));
+			return;
+		}
+		// This will throw an exception if types dont match
+		v.setValue(value);
+	}
+}
Index: /trunk/src/org/expeditee/simple/Primitives.java
===================================================================
--- /trunk/src/org/expeditee/simple/Primitives.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/Primitives.java	(revision 6)
@@ -0,0 +1,246 @@
+package org.expeditee.simple;
+
+public class Primitives extends Variables<SPrimitive<?>> {
+	private static final String[] prefixes = new String[] { SInteger.prefix,
+			SBoolean.prefix, SString.prefix, SReal.prefix, SCharacter.prefix };
+
+	public static boolean isPrimitive(String varName) {
+		for (String s : prefixes) {
+			if (varName.startsWith(s))
+				return true;
+		}
+		return false;
+	}
+
+	public static final int PREFIX_LENGTH = SInteger.prefix.length();
+
+	public Primitives() {
+		super();
+	}
+
+	protected String getType() {
+		return "primitive";
+	}
+
+	public void setValue(String variableName, SPrimitive newValue)
+			throws Exception {
+		try {
+			// if it is an existing variable change the value
+			SPrimitive v = getVariable(variableName);
+			v.setValue(newValue);
+		} catch (Exception e) {
+			// If the first variable doesnt exist then add it
+			add(variableName, newValue);
+		}
+	}
+
+	public void setValue(String variableName, String newValue) throws Exception {
+		setValue(variableName, new SString(newValue));
+	}
+
+	/**
+	 * Adds a variable to the list of primitives.
+	 * 
+	 * @param name
+	 *            the variable name
+	 * @param value
+	 *            a primitive to take the new variables value from
+	 * @throws Exception
+	 *             if the variable name is invalid
+	 */
+	public void add(String name, SPrimitive value) throws Exception {
+		// figure out the type and add it...
+		SPrimitive newVar;
+		if (name.startsWith(SInteger.prefix))
+			newVar = new SInteger();
+		else if (name.startsWith(SBoolean.prefix))
+			newVar = new SBoolean();
+		else if (name.startsWith(SReal.prefix))
+			newVar = new SReal();
+		else if (name.startsWith(SString.prefix))
+			newVar = new SString();
+		else if (name.startsWith(SCharacter.prefix))
+			newVar = new SCharacter();
+		else
+			throw new IncorrectTypeException(name, "primitive");
+		newVar.setName(name);
+		newVar.setValue(value);
+		list_.add(newVar);
+	}
+
+	public String getStringValue(String name) throws Exception {
+		return getVariable(name).stringValue();
+	}
+
+	public boolean getBooleanValue(String name) throws Exception {
+		return getVariable(name).booleanValue();
+	}
+
+	public long getIntegerValue(String name) throws Exception {
+		return getVariable(name).integerValue();
+	}
+
+	public double getDoubleValue(String name) throws Exception {
+		return getVariable(name).doubleValue();
+	}
+
+	public char getCharacterValue(String name) throws Exception {
+		return getVariable(name).characterValue();
+	}
+
+	/*
+	 * public String setStringValue(String name, String value) throws Exception {
+	 * return getVariable(name).stringValue(); }
+	 * 
+	 * public boolean setBooleanValue(String name, boolean value) throws
+	 * Exception { return getVariable(name).booleanValue(); }
+	 * 
+	 * public long setIntegerValue(String name, long value) throws Exception {
+	 * return getVariable(name).integerValue(); }
+	 * 
+	 * public double setDoubleValue(String name, double value) throws Exception {
+	 * return getVariable(name).doubleValue(); }
+	 * 
+	 * public double setCharacterValue(String name, char value) throws Exception {
+	 * return getVariable(name).characterValue(); }
+	 */
+
+	public static boolean isNumeric(String varName) {
+		return varName.startsWith(SInteger.prefix)
+				|| varName.startsWith(SReal.prefix);
+	}
+
+	public boolean equalValues(String var1, String var2) throws Exception {
+		return compareValues(var1, var2) == 0;
+	}
+
+	/**
+	 * Checks if the string representation of two variables is identical
+	 * ignoring case.
+	 * 
+	 * @param var1
+	 * @param var2
+	 * @return
+	 * @throws Exception
+	 */
+	public boolean equalValuesNoCase(String var1, String var2) throws Exception {
+		return getStringValue(var1).equalsIgnoreCase(getStringValue(var2));
+	}
+
+	/**
+	 * 
+	 * @param var1
+	 * @param var2
+	 * @return
+	 * @throws Exception
+	 */
+	public int compareValues(String var1, String var2) throws Exception {
+		// If one of them is a real and one is an integer then do a numeric
+		// comparison
+		if ((var1.startsWith(SInteger.prefix) || var2
+				.startsWith(SInteger.prefix))) {
+			long v1 = getIntegerValue(var1);
+			long v2 = getIntegerValue(var2);
+			return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+		} else if ((var1.startsWith(SReal.prefix) || var2
+				.startsWith(SReal.prefix))) {
+			double v1 = getDoubleValue(var1);
+			double v2 = getDoubleValue(var2);
+			return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+		} else if ((var1.startsWith(SBoolean.prefix) || var2
+				.startsWith(SBoolean.prefix))) {
+			boolean v1 = getBooleanValue(var1);
+			boolean v2 = getBooleanValue(var2);
+			return v1 == v2 ? 0 : (v1 == true ? 1 : -1);
+		}
+		return getStringValue(var1).compareTo(getStringValue(var2));
+	}
+
+	public void add(String var) throws Exception {
+		setValue(var, new SReal(getVariable(var).doubleValue() + 1));
+	}
+
+	public void subtract(String var) throws Exception {
+		setValue(var, new SReal(getVariable(var).doubleValue() - 1));
+	}
+
+	public void add(String toSet, String amount) throws Exception {
+		setValue(toSet, new SReal(getVariable(toSet).doubleValue()
+				+ getVariable(amount).doubleValue()));
+	}
+
+	public void subtract(String toSet, String amount) throws Exception {
+		setValue(toSet, new SReal(getVariable(toSet).doubleValue()
+				- getVariable(amount).doubleValue()));
+	}
+
+	public void multiply(String toSet, String amount) throws Exception {
+		setValue(toSet, new SReal(getVariable(toSet).doubleValue()
+				- getVariable(amount).doubleValue()));
+	}
+
+	public void divide(String toSet, String amount) throws Exception {
+		setValue(toSet, new SReal(getVariable(toSet).doubleValue()
+				- getVariable(amount).doubleValue()));
+	}
+
+	public void add(String var1, String var2, String varAnswer)
+			throws Exception {
+		setValue(varAnswer, new SReal(getVariable(var1).doubleValue()
+				+ getVariable(var2).doubleValue()));
+	}
+
+	public void subtract(String var1, String var2, String varAnswer)
+			throws Exception {
+		setValue(varAnswer, new SReal(getVariable(var1).doubleValue()
+				- getVariable(var2).doubleValue()));
+	}
+
+	public void divide(String var1, String var2, String varAnswer)
+			throws Exception {
+		setValue(varAnswer, new SReal(getVariable(var1).doubleValue()
+				/ getVariable(var2).doubleValue()));
+	}
+
+	public void multiply(String var1, String var2, String varAnswer)
+			throws Exception {
+		setValue(varAnswer, new SReal(getVariable(var1).doubleValue()
+				* getVariable(var2).doubleValue()));
+	}
+
+	public void modulo(String var1, String var2, String varAnswer)
+			throws Exception {
+		setValue(varAnswer, new SReal(getVariable(var1).integerValue()
+				% getVariable(var2).integerValue()));
+	}
+
+	public void power(String var1, String var2, String varAnswer)
+			throws Exception {
+		setValue(varAnswer, new SReal(Math.pow(getVariable(var1).doubleValue(),
+				getVariable(var2).doubleValue())));
+	}
+
+	public void log(String variable, String answer) throws Exception {
+		setValue(answer, new SReal(Math
+				.log(getVariable(variable).doubleValue())));
+	}
+
+	public void log10(String variable, String answer) throws Exception {
+		setValue(answer, new SReal(Math.log10(getVariable(variable)
+				.doubleValue())));
+	}
+
+	public void sqrt(String variable, String answer) throws Exception {
+		setValue(answer, new SReal(Math.sqrt(getVariable(variable)
+				.doubleValue())));
+	}
+
+	public void exp(String variable, String answer) throws Exception {
+		setValue(answer, new SReal(Math
+				.exp(getVariable(variable).doubleValue())));
+	}
+
+	public void not(String variable, String answer) throws Exception {
+		setValue(answer, new SBoolean(!getVariable(variable).booleanValue()));
+	}
+}
Index: /trunk/src/org/expeditee/simple/SBoolean.java
===================================================================
--- /trunk/src/org/expeditee/simple/SBoolean.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SBoolean.java	(revision 6)
@@ -0,0 +1,49 @@
+package org.expeditee.simple;
+
+public class SBoolean extends SPrimitive<Boolean> {
+	public static String prefix = SVariable.prefix + "b" + SVariable.separator;
+
+	public SBoolean() {
+		super();
+	}
+
+	public SBoolean(String name, Boolean value) {
+		super(name, value);
+	}
+
+	public SBoolean(Boolean value) throws Exception {
+		super(value);
+	}
+
+	@Override
+	public void parse(String s) {
+		String lowerCase = s.toLowerCase();
+		value_ = lowerCase.equals("true") || lowerCase.equals("t")
+				|| lowerCase.equals("yes") || lowerCase.equals("on");
+	}
+
+	@Override
+	public Boolean booleanValue() {
+		return value_;
+	}
+
+	@Override
+	public Long integerValue() {
+		return value_ ? 1L : 0L;
+	}
+
+	@Override
+	public Double doubleValue() {
+		return value_ ? 1.0 : 0.0;
+	}
+
+	@Override
+	public Character characterValue() {
+		return value_ ? 'T' : 'F';
+	}
+
+	@Override
+	public void setValue(SPrimitive v) throws Exception {
+		value_ = v.booleanValue();
+	}
+}
Index: /trunk/src/org/expeditee/simple/SCharacter.java
===================================================================
--- /trunk/src/org/expeditee/simple/SCharacter.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SCharacter.java	(revision 6)
@@ -0,0 +1,55 @@
+package org.expeditee.simple;
+
+public class SCharacter extends SPrimitive<Character> {
+	public static String prefix = SVariable.prefix + "c" + SVariable.separator;
+
+	public SCharacter() {
+		super();
+	}
+
+	public SCharacter(String name, Character value) {
+		super(name, value);
+	}
+
+	public SCharacter(Character value) throws Exception {
+		super(value);
+	}
+
+	@Override
+	public void parse(String s) {
+		if (s == "")
+			value_ = '\0';
+		else
+			value_ = s.charAt(0);
+	}
+
+	@Override
+	public Boolean booleanValue() {
+		return value_ == 'T' || value_ == 't';
+	}
+
+	@Override
+	public Long integerValue() {
+		return (long) value_.charValue();
+	}
+
+	@Override
+	public Double doubleValue() {
+		return (double) value_.charValue();
+	}
+
+	@Override
+	public String stringValue() {
+		return value_.toString();
+	}
+
+	@Override
+	public Character characterValue() {
+		return value_;
+	}
+
+	@Override
+	public void setValue(SPrimitive v) throws Exception {
+		value_ = v.characterValue();
+	}
+}
Index: /trunk/src/org/expeditee/simple/SInteger.java
===================================================================
--- /trunk/src/org/expeditee/simple/SInteger.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SInteger.java	(revision 6)
@@ -0,0 +1,58 @@
+package org.expeditee.simple;
+
+public class SInteger extends SPrimitive<Long> {
+	public static String prefix = SVariable.prefix + "i" + SVariable.separator;
+
+	public SInteger() {
+		super();
+	}
+
+	public SInteger(String name, Long value) {
+		super(name, value);
+	}
+
+	public SInteger(String name, Integer value) {
+		super(name, value.longValue());
+	}
+
+	public SInteger(long value) throws Exception {
+		super(value);
+	}
+
+	public SInteger(int value) throws Exception {
+		super((long) value);
+	}
+
+	@Override
+	public void parse(String s) throws Exception {
+		if (s == "")
+			value_ = 0L;
+		else
+			value_ = (long) Double.parseDouble(s);
+	}
+
+	@Override
+	public Boolean booleanValue() {
+		return value_ > 0;
+	}
+
+	@Override
+	public Long integerValue() {
+		return value_;
+	}
+
+	@Override
+	public Double doubleValue() {
+		return value_.doubleValue();
+	}
+
+	@Override
+	public Character characterValue() {
+		return new Character((char) value_.intValue());
+	}
+
+	@Override
+	public void setValue(SPrimitive v) throws Exception {
+		value_ = v.integerValue();
+	}
+}
Index: /trunk/src/org/expeditee/simple/SPointer.java
===================================================================
--- /trunk/src/org/expeditee/simple/SPointer.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SPointer.java	(revision 6)
@@ -0,0 +1,35 @@
+package org.expeditee.simple;
+
+public class SPointer<T> extends SVariable<T> {
+
+	public static final String framePrefix = SVariable.prefix + "fp"
+			+ SVariable.separator;
+
+	public static final String itemPrefix = SVariable.prefix + "ip"
+			+ SVariable.separator;
+
+	public static final String filePrefix = SVariable.prefix + "f"
+			+ SVariable.separator;
+
+	public SPointer(String name, T value) {
+		super(name, value);
+	}
+
+	public void setValue(T newValue) {
+		value_ = newValue;
+	}
+
+	@Override
+	public String stringValue() {
+		return value_.toString();
+	}
+
+	@Override
+	public void setValue(SVariable<T> v) throws Exception {
+		if (v instanceof SPointer) {
+			setValue(v.getValue());
+			return;
+		}
+		throw new Exception("Can not set pointer variable with primitive");
+	}
+}
Index: /trunk/src/org/expeditee/simple/SPrimitive.java
===================================================================
--- /trunk/src/org/expeditee/simple/SPrimitive.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SPrimitive.java	(revision 6)
@@ -0,0 +1,55 @@
+package org.expeditee.simple;
+
+public abstract class SPrimitive<T> extends SVariable<T> {
+
+	public SPrimitive(String name, T value) {
+		super(name, value);
+	}
+
+	public SPrimitive(T value) {
+		super(null, value);
+	}
+
+	public SPrimitive() {
+		super();
+	}
+
+	public Long integerValue() throws Exception {
+		throw new Exception("Can not convert " + this.getClass().getName()
+				+ " to Integer");
+	}
+
+	public Boolean booleanValue() throws Exception {
+		throw new Exception("Can not convert " + this.getClass().getName()
+				+ " to Boolean");
+	}
+
+	public Double doubleValue() throws Exception {
+		throw new Exception("Can not convert " + this.getClass().getName()
+				+ " to Real");
+	}
+
+	public Character characterValue() throws Exception {
+		throw new Exception("Can not convert " + this.getClass().getName()
+				+ " to Character");
+	}
+
+	public abstract void setValue(SPrimitive v) throws Exception;
+
+	@Override
+	public void setValue(SVariable<T> v) throws Exception {
+		if (v instanceof SPrimitive) {
+			setValue((SPrimitive<?>) v);
+			return;
+		}
+		throw new Exception("Can not set primitive variable with pointer");
+	}
+
+	/**
+	 * Sets the value of the primitive using a string.
+	 * 
+	 * @param s
+	 * @throws Exception
+	 */
+	public abstract void parse(String s) throws Exception;
+}
Index: /trunk/src/org/expeditee/simple/SReal.java
===================================================================
--- /trunk/src/org/expeditee/simple/SReal.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SReal.java	(revision 6)
@@ -0,0 +1,54 @@
+package org.expeditee.simple;
+
+public class SReal extends SPrimitive<Double> {
+	public static String prefix = SVariable.prefix + "r" + SVariable.separator;
+
+	public SReal() {
+		super();
+	}
+
+	public SReal(String name, Double value) {
+		super(name, value);
+	}
+
+	public SReal(double value) throws Exception {
+		super(value);
+	}
+
+	public SReal(float value) throws Exception {
+		super((double) value);
+	}
+
+	@Override
+	public void parse(String s) throws Exception {
+		if (s == "")
+			value_ = 0.0;
+		else
+			value_ = Double.parseDouble(s);
+	}
+
+	@Override
+	public Boolean booleanValue() {
+		return value_ > 0;
+	}
+
+	@Override
+	public Long integerValue() {
+		return value_.longValue();
+	}
+
+	@Override
+	public Double doubleValue() {
+		return value_;
+	}
+
+	@Override
+	public Character characterValue() {
+		return new Character((char) value_.intValue());
+	}
+
+	@Override
+	public void setValue(SPrimitive v) throws Exception {
+		value_ = v.doubleValue();
+	}
+}
Index: /trunk/src/org/expeditee/simple/SString.java
===================================================================
--- /trunk/src/org/expeditee/simple/SString.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SString.java	(revision 6)
@@ -0,0 +1,59 @@
+package org.expeditee.simple;
+
+public class SString extends SPrimitive<String> {
+	public static String prefix = SVariable.prefix + "s" + SVariable.separator;
+
+	public SString() {
+		super();
+	}
+
+	public SString(String name, String value) {
+		super(name, value);
+	}
+
+	public SString(String value) throws Exception {
+		super(value);
+	}
+
+	@Override
+	public void parse(String s) {
+		value_ = s;
+	}
+
+	@Override
+	public Boolean booleanValue() {
+		return Boolean.parseBoolean(value_);
+	}
+
+	@Override
+	public Long integerValue() {
+		if (value_ == "")
+			return 0L;
+		return (long) Double.parseDouble(value_);
+	}
+
+	@Override
+	public Double doubleValue() {
+		if (value_ == "")
+			return 0.0;
+		return Double.parseDouble(value_);
+	}
+
+	@Override
+	public String stringValue() {
+		return value_;
+	}
+
+	@Override
+	public Character characterValue() {
+		if (value_.length() > 0)
+			return value_.charAt(0);
+
+		return '\0';
+	}
+
+	@Override
+	public void setValue(SPrimitive v) {
+		value_ = v.stringValue();
+	}
+}
Index: /trunk/src/org/expeditee/simple/SVariable.java
===================================================================
--- /trunk/src/org/expeditee/simple/SVariable.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/SVariable.java	(revision 6)
@@ -0,0 +1,43 @@
+package org.expeditee.simple;
+
+/**
+ * Class for a generic variable.
+ * 
+ * @author mrww1
+ * 
+ */
+public abstract class SVariable<T> {
+	public static final String prefix = "$";
+
+	protected static final String separator = ".";
+
+	protected String name_;
+
+	protected T value_;
+
+	public SVariable(String name, T value) {
+		name_ = name;
+		value_ = value;
+	}
+
+	public SVariable() {
+	}
+
+	public String getName() {
+		return name_;
+	}
+
+	public T getValue() {
+		return value_;
+	}
+
+	protected void setName(String newName) {
+		name_ = newName;
+	}
+
+	public String stringValue() {
+		return value_.toString();
+	}
+
+	public abstract void setValue(SVariable<T> v) throws Exception;
+}
Index: /trunk/src/org/expeditee/simple/UnitTestFailedException.java
===================================================================
--- /trunk/src/org/expeditee/simple/UnitTestFailedException.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/UnitTestFailedException.java	(revision 6)
@@ -0,0 +1,9 @@
+package org.expeditee.simple;
+
+public class UnitTestFailedException extends RuntimeException {
+	static final long serialVersionUID = -7034897190745766939L;
+
+	public UnitTestFailedException(String expected, String result) {
+		super("Assert expected [" + expected + "] got [" + result + "]");
+	}
+}
Index: /trunk/src/org/expeditee/simple/VariableNotFoundException.java
===================================================================
--- /trunk/src/org/expeditee/simple/VariableNotFoundException.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/VariableNotFoundException.java	(revision 6)
@@ -0,0 +1,9 @@
+package org.expeditee.simple;
+
+public class VariableNotFoundException extends RuntimeException {
+	static final long serialVersionUID = -7034897190745766939L;
+
+	public VariableNotFoundException(String s) {
+		super(s);
+	}
+}
Index: /trunk/src/org/expeditee/simple/Variables.java
===================================================================
--- /trunk/src/org/expeditee/simple/Variables.java	(revision 6)
+++ /trunk/src/org/expeditee/simple/Variables.java	(revision 6)
@@ -0,0 +1,70 @@
+package org.expeditee.simple;
+
+import java.util.ArrayList;
+
+import org.expeditee.gui.FrameGraphics;
+
+public abstract class Variables<T extends SVariable<?>> {
+
+	protected ArrayList<T> list_;
+
+	public Variables() {
+		list_ = new ArrayList<T>();
+	}
+
+	public int size() {
+		return list_.size();
+	}
+
+	public void clear() {
+		list_.clear();
+	}
+
+	public void display() {
+		for (T v : list_) {
+			FrameGraphics.DisplayMessage(v.getName() + ": " + v.stringValue());
+		}
+	}
+
+	public T getVariable(String name) throws Exception {
+		for (T v : list_) {
+			if (v.getName().equalsIgnoreCase(name)) {
+				return v;
+			}
+		}
+		throw new VariableNotFoundException(name + " is not an existing "
+				+ getType());
+	}
+
+	/**
+	 * Sets the value of variable1 to value of variable2. If variable1 doesnt
+	 * exist then it is created.
+	 * 
+	 * @param variableToSet-
+	 *            the variable whos value will be modified.
+	 * @param variableWithNewValue-
+	 *            the variable to get the new value from.
+	 */
+	public void set(String variableToSet, String variableWithNewValue)
+			throws Exception {
+		// If the variables are the same then do nothing
+		if (variableToSet.equalsIgnoreCase(variableWithNewValue))
+			return;
+
+		T toGetValueFrom = getVariable(variableWithNewValue);
+		T toSet = null;
+		try {
+			toSet = getVariable(variableToSet);
+		} catch (Exception e) {
+			// if the variable to be set doesnt exist and exception is
+			// thrown So it will be added
+			add(variableToSet, toGetValueFrom);
+			return;
+		}
+		toSet.setValue(toGetValueFrom);
+	}
+
+	public abstract void add(String name, T value) throws Exception;
+
+	protected abstract String getType();
+}
Index: /trunk/src/org/expeditee/stats/AgentStats.java
===================================================================
--- /trunk/src/org/expeditee/stats/AgentStats.java	(revision 6)
+++ /trunk/src/org/expeditee/stats/AgentStats.java	(revision 6)
@@ -0,0 +1,77 @@
+package org.expeditee.stats;
+
+import java.util.Date;
+
+public class AgentStats {
+
+	private static int _ItemsExecuted = 0;
+
+	private static int _FramesExecuted = 0;
+
+	private static String SECONDS = "sec";
+
+	private static Date _StartTime = new Date();
+
+	private static String getTimeElapsedString() {
+		return "Time: " + getSecondsElapsed() + SECONDS;
+	}
+
+	public static long getMilliSecondsElapsed() {
+		return new Date().getTime() - _StartTime.getTime();
+	}
+
+	public static String getStats() {
+		StringBuffer stats = new StringBuffer();
+		stats.append("Run ").append(getTimeElapsedString()).append(
+				", Processed ");
+		appendStat(stats, "frames", _FramesExecuted, ", ");
+		appendStat(stats, "items", _ItemsExecuted, "");
+		return stats.toString();
+	}
+
+	private static double getSecondsElapsed() {
+		return (new Date().getTime() - _StartTime.getTime()) / 1000.0;
+	}
+
+	/**
+	 * Appends a single stats to a string buffer containing a collection of
+	 * stats.
+	 * 
+	 * @param stats
+	 *            The string buffer to append the stat onto
+	 * @param name
+	 *            The name of the stat
+	 * @param value
+	 *            The new value for the stat
+	 */
+	private static void appendStat(StringBuffer stats, String name, int value,
+			String separator) {
+		// prevent divide by zero errors
+		if (value < 0)
+			value = 0;
+
+		int perSec = (int) (value / Math.max(0.001, getSecondsElapsed()));
+
+		stats.append(value).append(" ").append(name).append(" @ ").append(
+				perSec).append("/" + SECONDS);
+		stats.append(separator);
+	}
+
+	public static void reset() {
+		_StartTime = new Date();
+		_FramesExecuted = 0;
+		_ItemsExecuted = 0;
+	}
+
+	/**
+	 * Called signal that a frame has been accessed.
+	 * 
+	 */
+	public static void FrameExecuted() {
+		_FramesExecuted++;
+	}
+
+	public static void ItemExecuted() {
+		_ItemsExecuted++;
+	}
+}
Index: /trunk/src/org/expeditee/stats/SessionStats.java
===================================================================
--- /trunk/src/org/expeditee/stats/SessionStats.java	(revision 6)
+++ /trunk/src/org/expeditee/stats/SessionStats.java	(revision 6)
@@ -0,0 +1,279 @@
+package org.expeditee.stats;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.sql.Time;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.expeditee.gui.DisplayIO;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.gui.TimeKeeper;
+import org.expeditee.io.Logger;
+
+public class SessionStats {
+
+	private static final long MILLISECONDS_PER_MINUTE = 6000;
+
+	private static Date _FrameAccessTime = new Date();
+
+	private static List<String> _FrameEvents = new ArrayList<String>();
+
+	// statistics counters
+	private static final int MOUSE_BUTTONS = 3;
+
+	private static StringBuffer _FramesEdited = new StringBuffer();
+
+	private static int[] _MouseCounters = new int[MOUSE_BUTTONS];
+
+	private static int _AccessedFrames = -1;
+
+	private static int _SavedFrames = 0;
+
+	// the number of frames created via TDFC
+	private static int _CreatedFrames = 0;
+
+	// character counts
+	private static int[] _CharCounts = new int[255];
+
+	// how many items have been created thus far
+	private static int _CreatedItems = 0;
+
+	private static Date _StartTime = new Date();
+	
+	private static long _LastEvent = new Date().getTime();
+	
+	private static Time _DarkTime = new Time(0);
+	
+	private static final long DARK_TIME_THRESHOLD = 60000;
+
+	private static int _EscapeCount = 0;
+
+	private static boolean _StatsEnabled = true;
+
+	public static String getCurrentStats() {
+		StringBuffer stats = new StringBuffer("Date: ");
+
+		stats.append(Logger.EasyDateFormat("ddMMMyyyy")).append("\n");
+		stats.append("SessionTime: ").append(getTimeElapsed()).append("\n");
+		stats.append("DarkTime: ").append( new SimpleDateFormat("m").format(_DarkTime) ).append("\n");
+		
+		stats.append(getFrameStats());
+		stats.append(getResponseStats()).append("\n");
+
+		stats.append(getCharacterStats());
+
+		stats.append("Version: ").append(DisplayIO.TITLE);
+
+		return stats.toString();
+	}
+
+	private static String getTimeElapsed() {
+		SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");
+		Date currentTime = new Date();
+		Time elapsedTime = new Time(currentTime.getTime()-_StartTime.getTime());
+		String time = formatter.format(_StartTime);
+		time += " --> " + formatter.format(currentTime);
+		time += " = " + (new SimpleDateFormat("m").format(elapsedTime));
+
+		return time;
+	}
+
+	private static String getResponseStats() {
+		String stats = "ResponseTime: "
+				+ TimeKeeper.Formatter.format(FrameUtils.getLastResponseTime())
+				+ "sec, ";
+
+		if (_AccessedFrames > 0)
+			stats += TimeKeeper.Formatter.format(FrameUtils
+					.getResponseTimeTotal()
+					/ _AccessedFrames)
+					+ "avg";
+		else
+			stats += "--avg";
+
+		return stats;
+	}
+
+	private static String getFrameStats() {
+		return getFrameStats(true);
+	}
+
+	private static String getFrameStats(boolean newline) {
+		StringBuffer stats = new StringBuffer();
+		appendStat(stats, "FramesAccessed", _AccessedFrames, newline);
+		appendStat(stats, "FramesEdited", _SavedFrames, newline);
+		return stats.toString();
+	}
+
+	private static int getMinutesUsed() {
+		long elapsedTime = new Date().getTime() - _StartTime.getTime() - _DarkTime.getTime();
+		
+		return (int) (elapsedTime / MILLISECONDS_PER_MINUTE);
+	}
+
+	private static String getCharacterStats() {
+		int[] counts = _CharCounts;
+
+		int chars = 0;
+
+		for (int i = 'A'; i <= 'Z'; i++)
+			chars += counts[i];
+
+		for (int i = 'a'; i <= 'z'; i++)
+			chars += counts[i];
+
+		for (int i = '0'; i <= '9'; i++)
+			chars += counts[i];
+
+		chars -= counts[KeyEvent.VK_BACK_SPACE];
+		chars -= counts[KeyEvent.VK_DELETE];
+
+		int EOS = counts['.'] + counts[','] + counts['!'] + counts['?'];
+
+		int punct = counts[' '] + counts[';'] + counts[':'];
+		chars += counts['('] + counts[')'] + counts['\''] + counts['"']
+				+ counts['+'] + counts['='] + counts['-'];
+
+		StringBuffer stats = new StringBuffer();
+		appendStat(stats, "Chars", chars + punct + EOS);
+		appendStat(stats, "Words", punct + EOS);
+		appendStat(stats, "Sentences", EOS);
+		appendStat(stats, "Items", _CreatedItems);
+		appendStat(stats, "Frames", _CreatedFrames);
+		appendStat(stats, "Escape", _EscapeCount);
+		appendStat(stats, "Left", _MouseCounters[MouseEvent.BUTTON1 - 1]);
+		appendStat(stats, "Middle", _MouseCounters[MouseEvent.BUTTON2 - 1]);
+		appendStat(stats, "Right", _MouseCounters[MouseEvent.BUTTON3 - 1]);
+
+		return stats.toString();
+	}
+
+	/**
+	 * Appends a single stats to a string buffer containing a collection of
+	 * stats.
+	 * 
+	 * @param stats
+	 *            The string buffer to append the stat onto
+	 * @param name
+	 *            The name of the stat
+	 * @param value
+	 *            The new value for the stat
+	 */
+	private static void appendStat(StringBuffer stats, String name, int value,
+			boolean newline) {
+		// prevent divide by zero errors
+		if (value < 0)
+			value = 0;
+
+		int perHour = value * 60 / Math.max(1, getMinutesUsed());
+
+		stats.append(value).append(" ").append(name).append(" @ ").append(perHour).append("/hour");
+		if (newline)
+			stats.append("\n");
+		else
+			stats.append(" ");
+
+	}
+
+	private static void appendStat(StringBuffer stats, String name, int value) {
+		appendStat(stats, name, value, true);
+	}
+
+	public static void resetStats() {
+		_StartTime = new Date();
+		_AccessedFrames = 0;
+		_SavedFrames = 0;
+		_CreatedFrames = 0;
+		_CharCounts = new int[255];
+		_CreatedItems = 0;
+		_EscapeCount = 0;
+		_DarkTime.setTime(0);
+		_MouseCounters = new int[MOUSE_BUTTONS];
+	}
+
+	/**
+	 * Called signal that a frame has been accessed.
+	 * 
+	 */
+	public static void AccessedFrame() {
+		if (_StatsEnabled) {
+			_AccessedFrames++;
+			_FrameEvents.clear();
+			_FrameAccessTime = new Date();
+		}
+	}
+
+	public static void SavedFrame(String frameName) {
+		FrameEdited(frameName);
+		_SavedFrames++;
+	}
+
+	public static void Escape() {
+		_EscapeCount++;
+	}
+
+	public static void CreatedFrame() {
+		_CreatedFrames++;
+	}
+
+	public static void CreatedItem() {
+		_CreatedItems++;
+	}
+
+	/**
+	 * Increments the count for a character that is typed.
+	 * 
+	 * @param ch
+	 *            ascii value for the typed character
+	 */
+	public static void TypedChar(int ch) {
+		UserEvent();
+		_CharCounts[ch]++;
+	}
+	
+	private static void UserEvent() {
+		long thisEvent = new Date().getTime();
+		long elapsedTime = thisEvent - _LastEvent;
+		if (elapsedTime > DARK_TIME_THRESHOLD)
+			_DarkTime.setTime(_DarkTime.getTime() + elapsedTime);
+		_LastEvent = thisEvent;
+	}
+
+	public static void AddFrameEvent(String description) {
+		Date elapsedTime = new Date((new Date()).getTime()
+				- _FrameAccessTime.getTime());
+
+		_FrameEvents.add(Logger.EasyDateFormat("mm:ss", elapsedTime) + " "
+				+ DisplayIO.getMouseX() + " " + DisplayIO.getMouseY() + " "
+				+ description);
+	}
+
+	public static String getFrameEventList() {
+		StringBuilder eventList = new StringBuilder();
+		for (String s : _FrameEvents)
+			eventList.append(s + '\n');
+
+		return eventList.toString();
+	}
+
+	public static void MouseClicked(int button) {
+		UserEvent();
+		_MouseCounters[button - 1]++;
+	}
+
+	public static void setEnabled(boolean value) {
+		_StatsEnabled = true;
+	}
+
+	private static void FrameEdited(String name) {
+		_FramesEdited.append(Logger.EasyDateFormat("ddMMMyyyy-HHmm:ss"))
+				.append("[").append(name).append("]\n");
+	}
+
+	public static String getFramesEdited() {
+		return _FramesEdited.toString();
+	}
+}
Index: /trunk/src/org/expeditee/stats/StatsLogger.java
===================================================================
--- /trunk/src/org/expeditee/stats/StatsLogger.java	(revision 6)
+++ /trunk/src/org/expeditee/stats/StatsLogger.java	(revision 6)
@@ -0,0 +1,70 @@
+package org.expeditee.stats;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import org.expeditee.io.Logger;
+
+/**
+ * This class is used to create all stats log files. Files are created with
+ * filename: Date-Time.stat<br>
+ * Where Date is in the format DDMMMYYYY and Time is in the format HHMM.
+ * 
+ * @author mrww1
+ * 
+ */
+public class StatsLogger {
+
+	private static String _filename = null;
+	private static final String FRAMES_EDITED_FILENAME = "FramesEdited.stat";
+
+	/**
+	 * Sets the path on disk that the statlog files will be created in.
+	 * 
+	 * @param location
+	 *            The path on disk to create the log files in.
+	 */
+	private static void Init() {
+		if (!org.expeditee.gui.UserSettings.LogStats)
+			return;
+
+		File test = new File(org.expeditee.gui.FrameIO.STATISTICS_DIR);
+		if (!test.exists())
+			test.mkdir();
+
+		_filename = Logger.EasyDateFormat("ddMMMyy-HHmm") + ".stat";
+	}
+
+	/**
+	 * Writes the current stats to a file.
+	 * 
+	 */
+	public static void WriteStatsFile() {
+		if (!org.expeditee.gui.UserSettings.LogStats)
+			return;
+
+		Init();
+
+		try {
+			BufferedWriter writer = new BufferedWriter(new FileWriter(
+					org.expeditee.gui.FrameIO.STATISTICS_DIR + _filename, true));
+			writer.write(SessionStats.getCurrentStats());
+			writer.newLine();
+
+			writer.flush();
+			writer.close();
+			
+			writer = new BufferedWriter(new FileWriter(
+					org.expeditee.gui.FrameIO.STATISTICS_DIR + FRAMES_EDITED_FILENAME, true));
+			writer.write(SessionStats.getFramesEdited());
+			writer.newLine();
+
+			writer.flush();
+			writer.close();
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+		}
+	}
+}
