changeset 3:9e67171477bc

PHANTOM Omni Heptic device release
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Wed, 25 Apr 2012 17:09:09 +0100
parents 4b2f975e35fa
children 2c67ac862920
files README.txt java/.classpath java/libs/cmu_time_awb.jar java/libs/cmu_us_kal.jar java/libs/cmudict04.jar java/libs/cmulex.jar java/libs/cmutimelex.jar java/libs/en_us.jar java/libs/freetts.jar java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTree.java java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTreeCellRenderer.java java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/SetProperties.java java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/XMLHandler.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionListener.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionModel.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramEdge.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramElement.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModelTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramTreeNodeEvent.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramTreeNodeListener.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceHolderMutableTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceMutableTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ElementChangedEvent.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/MalformedEdgeException.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeProperties.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeReferenceMutableTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyMutableTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyTypeMutableTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TreeModel.java java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TypeMutableTreeNode.java java/src/uk/ac/qmul/eecs/ccmi/gui/AudioFeedback.java java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.java java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.properties java/src/uk/ac/qmul/eecs/ccmi/gui/Diagram.java java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramEventSource.java java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramModelUpdater.java java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramPanel.java java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java java/src/uk/ac/qmul/eecs/ccmi/gui/Direction.java java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java java/src/uk/ac/qmul/eecs/ccmi/gui/EdgePopupMenu.java java/src/uk/ac/qmul/eecs/ccmi/gui/EdgePopupMenu.properties java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties java/src/uk/ac/qmul/eecs/ccmi/gui/EditorTabbedPane.java java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java java/src/uk/ac/qmul/eecs/ccmi/gui/Finder.java java/src/uk/ac/qmul/eecs/ccmi/gui/GraphElement.java java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java java/src/uk/ac/qmul/eecs/ccmi/gui/HapticKindle.java java/src/uk/ac/qmul/eecs/ccmi/gui/HapticTrigger.java java/src/uk/ac/qmul/eecs/ccmi/gui/LineStyle.java java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java java/src/uk/ac/qmul/eecs/ccmi/gui/NodePopupMenu.java java/src/uk/ac/qmul/eecs/ccmi/gui/NodePopupMenu.properties java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyEditorDialog.java java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechLogDialog.java java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechOptionPane.java java/src/uk/ac/qmul/eecs/ccmi/gui/TemplateEditor.java java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessFilter.java java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessFilter.properties java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanel.java java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanelEditor.java java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessTextPane.java java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/BroadcastFilter.java java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/BroadcastFilterTree.xml java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/DisplayFilter.java java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/DisplayFilterTree.xml java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTree.java java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.java java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.properties java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java java/src/uk/ac/qmul/eecs/ccmi/haptics/DummyHaptics.java java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListenerCommand.java java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.dll java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java java/src/uk/ac/qmul/eecs/ccmi/haptics/Node.java java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.properties java/src/uk/ac/qmul/eecs/ccmi/main/Organization Chart.xml java/src/uk/ac/qmul/eecs/ccmi/main/Tube.xml java/src/uk/ac/qmul/eecs/ccmi/main/UML Diagram.xml java/src/uk/ac/qmul/eecs/ccmi/network/AwarenessMessage.java java/src/uk/ac/qmul/eecs/ccmi/network/CCmIOSCPacketCodec.java java/src/uk/ac/qmul/eecs/ccmi/network/ClientConnectionManager.java java/src/uk/ac/qmul/eecs/ccmi/network/Command.java java/src/uk/ac/qmul/eecs/ccmi/network/CommandExecutor.java java/src/uk/ac/qmul/eecs/ccmi/network/DiagramDownloader.java java/src/uk/ac/qmul/eecs/ccmi/network/DiagramEventActionSource.java java/src/uk/ac/qmul/eecs/ccmi/network/LockMessage.java java/src/uk/ac/qmul/eecs/ccmi/network/Message.java java/src/uk/ac/qmul/eecs/ccmi/network/NetDiagram.java java/src/uk/ac/qmul/eecs/ccmi/network/NetworkThread.java java/src/uk/ac/qmul/eecs/ccmi/network/OscProtocol.java java/src/uk/ac/qmul/eecs/ccmi/network/Protocol.java java/src/uk/ac/qmul/eecs/ccmi/network/Reply.java java/src/uk/ac/qmul/eecs/ccmi/network/Server.java java/src/uk/ac/qmul/eecs/ccmi/network/Server.properties java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java java/src/uk/ac/qmul/eecs/ccmi/network/ServerLockManager.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EdgeDrawSupport.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeEdge.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.java java/src/uk/ac/qmul/eecs/ccmi/sound/BeadsSound.java java/src/uk/ac/qmul/eecs/ccmi/sound/Sound.java java/src/uk/ac/qmul/eecs/ccmi/speech/BeadsAudioPlayer.java java/src/uk/ac/qmul/eecs/ccmi/speech/DummyNarrator.java java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.java java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.properties java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java java/src/uk/ac/qmul/eecs/ccmi/speech/TreeSonifier.java java/src/uk/ac/qmul/eecs/ccmi/speech/WinNarrator.dll java/src/uk/ac/qmul/eecs/ccmi/utils/GridBagUtilities.java java/src/uk/ac/qmul/eecs/ccmi/utils/InteractionLog.java java/src/uk/ac/qmul/eecs/ccmi/utils/NativeLibFileWriter.java java/src/uk/ac/qmul/eecs/ccmi/utils/ResourceFileWriter.java native/PhantomOmni/CollectionsManager.cpp native/PhantomOmni/CollectionsManager.h native/PhantomOmni/GraphicManager.cpp native/PhantomOmni/GraphicManager.h native/PhantomOmni/HapticException.cpp native/PhantomOmni/HapticException.h native/PhantomOmni/HapticManager.cpp native/PhantomOmni/HapticManager.h native/PhantomOmni/Haptics.vcproj native/PhantomOmni/stdafx.cpp native/PhantomOmni/stdafx.h native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_Haptics.cpp native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_Haptics.h native/PhantomOmni/utils.cpp native/PhantomOmni/utils.h native/WinNarrator/WinNarrator.vcproj
diffstat 140 files changed, 13194 insertions(+), 5081 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,15 @@
+The CCmI Editor is a Collaborative Cross-Modal Diagram Editing Tool.
+  
+Copyright of the source code belongs to Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+and, where expressly specified, to Prof. Cay Horstmann (http://horstmann.com).
+
+The software is released under the GNU General Public License (version 3.0) with the exception of the code 
+in the "native/PhantomOmni" folder. The code in this folder, used to generate the native windows library 
+(.dll) that handles the PHANTOM Omni® Haptic Device, is a derivative work in accordance with the OpenHaptics® 
+Academic Edition License Agreement (see http://www.sensable.com/ for further information), a non-free license. 
+
+Clearly the use of a non-free library is a drawback. If you are thinking of doing substantial further 
+work on the program, please first free it from dependence on the non-free library.
+
+PHANTOM Omni® and OpenHaptics® are registered trademarks of Sensable Technologies, Inc®. 
+
--- a/java/.classpath	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/.classpath	Wed Apr 25 17:09:09 2012 +0100
@@ -3,6 +3,13 @@
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
 	<classpathentry kind="lib" path="libs/beads.jar"/>
+	<classpathentry kind="lib" path="libs/cmu_time_awb.jar"/>
+	<classpathentry kind="lib" path="libs/cmu_us_kal.jar"/>
+	<classpathentry kind="lib" path="libs/cmudict04.jar"/>
+	<classpathentry kind="lib" path="libs/cmulex.jar"/>
+	<classpathentry kind="lib" path="libs/cmutimelex.jar"/>
+	<classpathentry kind="lib" path="libs/en_us.jar"/>
+	<classpathentry kind="lib" path="libs/freetts.jar"/>
 	<classpathentry kind="lib" path="libs/jl1.0.1.jar"/>
 	<classpathentry kind="lib" path="libs/JWizardComponent.jar"/>
 	<classpathentry kind="lib" path="libs/mp3spi1.9.4.jar"/>
Binary file java/libs/cmu_time_awb.jar has changed
Binary file java/libs/cmu_us_kal.jar has changed
Binary file java/libs/cmudict04.jar has changed
Binary file java/libs/cmulex.jar has changed
Binary file java/libs/cmutimelex.jar has changed
Binary file java/libs/en_us.jar has changed
Binary file java/libs/freetts.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTree.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,158 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.checkboxtree;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.SAXException;
+
+
+/**
+ * A JTree containing {@code CheckBoxTreeNode} nodes. The tree is built according to an XML file
+ * passed as argument to the constructor.
+ * The XML file hierarchical structure reflects the structure of the tree. XML tag name can be 
+ * either {@code selectable} or {@code unselectable}. The former representing tree nodes with a check
+ * box associated to it, and the latter a normal tree node, much as a {@link DefaultMutableTreeNode}. 
+ * Either tags must have an attribute {@code value}, representing the name of the node which will be displayed
+ * in the tree. Here is an example of a simple XML file, representing a tree with the tree root (non selectable) having
+ * three children, the first of which has, in turn, a child. all the descendants of the root are selectable, but the 
+ * first child.   
+ *    
+ * <pre>
+ * {@code
+ * 	<?xml version="1.0" encoding="utf-8"?>
+ *  <unselectable value="root">
+ *   <unselectable value="first child"/>
+ *   <selectable value="second child"/>
+ *   <selectable value "third child">
+ *     <selectable value="grand child"/>
+ *   </selectable>
+ *  </unselectable>
+ * }
+ * </pre>   
+ * 
+ * @see CheckBoxTreeNode 
+ */
+@SuppressWarnings("serial")
+public class CheckBoxTree extends JTree {
+	public CheckBoxTree(InputStream stream, SetProperties values){
+		super(new DefaultTreeModel(new DefaultMutableTreeNode()));
+		this.properties = values;
+		getAccessibleContext().setAccessibleName("tree");
+		treeModel =  (DefaultTreeModel)getModel();
+		setCellRenderer(new CheckBoxTreeCellRenderer());
+		buildTree(stream);
+		/* mouse listener to toggle the selected tree node */
+		addMouseListener(new MouseAdapter(){
+			@Override
+			public void mousePressed(MouseEvent e){
+				TreePath path = getPathForLocation(e.getX(),e.getY());
+				if(path == null)
+					return;
+				CheckBoxTreeNode treeNode = (CheckBoxTreeNode)path.getLastPathComponent();
+				toggleSelection(treeNode);
+			}
+		});
+	}
+	
+	/**
+	 * Builds a CheckBoxTree out of an xml file passed as argument. All the nodes 
+	 * of the tree are marked unchecked. 
+	 * 
+	 * @param stream an input stream to an xml file
+	 */
+	public CheckBoxTree(InputStream stream){
+		this(stream,null);
+	}
+	
+	/* use a sax parser to build the tree, if an error occurs during the parsing it just stops */
+	private void buildTree(InputStream stream){
+		SAXParserFactory factory = SAXParserFactory.newInstance();
+		try {
+			SAXParser saxParser = factory.newSAXParser();
+			XMLHandler handler = new XMLHandler((DefaultTreeModel)getModel(),properties);
+			saxParser.parse(stream, handler);
+		} catch (IOException e) {
+			e.printStackTrace();
+			return;
+		} catch (ParserConfigurationException e) {
+			e.printStackTrace();
+			return;
+		} catch (SAXException e) {
+			e.printStackTrace();
+			return;
+		}
+	}
+	
+	/**
+	 * Returns a reference to the properties holding which nodes of the tree are currently checked.  
+	 * @return the properties or {@value null} if the constructor with no properties was used. 
+	 */
+	public SetProperties getProperties(){
+		return properties;
+	}
+	
+	/**
+	 * Toggle the check box of the tree node passed as argument. If the tree node 
+	 * is not a leaf, then all its descendants will get the new value it. That is, 
+	 * if the tree node becomes selected as a result of the call, then all the descendants 
+	 * will become in turn selected (regardless of their previous state). If it becomes
+	 * unselected, the descendants will become unselected as well.   
+	 * 
+	 * @param treeNode the tree node to toggle
+	 */
+	public void toggleSelection(CheckBoxTreeNode treeNode){
+		if(treeNode.isSelectable()){
+			boolean selection = !treeNode.isSelected();
+			treeNode.setSelected(selection);
+			if(selection)
+				properties.add(treeNode.getPathAsString());
+			else
+				properties.remove(treeNode.getPathAsString());
+			treeModel.nodeChanged(treeNode);
+			if(!treeNode.isLeaf()){
+				for( @SuppressWarnings("unchecked")
+					Enumeration<CheckBoxTreeNode> enumeration = treeNode.depthFirstEnumeration(); enumeration.hasMoreElements();){
+					CheckBoxTreeNode t = enumeration.nextElement();
+					t.setSelected(selection);
+					if(selection)
+						properties.add(t.getPathAsString());
+					else
+						properties.remove(t.getPathAsString());
+					treeModel.nodeChanged(t);
+				}
+			}
+		}
+	}
+	
+	private SetProperties properties;
+	private DefaultTreeModel treeModel; 
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTreeCellRenderer.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,75 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.checkboxtree;
+
+import java.awt.Component;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+
+/**
+ * A tree cell renderer which renders {@code CheckBoxTreeNode} objects like a {@code JCheckBox}:
+ * thick box (showing whether the node is selected or not) followed by a label (same label it would
+ * appear with a default {@code DefaultTreeCellRenderer}.
+ *
+ */
+@SuppressWarnings("serial")
+public class CheckBoxTreeCellRenderer extends DefaultTreeCellRenderer {
+	
+	public CheckBoxTreeCellRenderer(){
+		checkBox = new JCheckBox();
+		checkBox.setBorder(BorderFactory.createLineBorder(this.getBorderSelectionColor()));
+	}
+	/**
+	 * Returns the {@code Component} that the renderer uses to draw the value
+	 * 
+	 * @throws ClassCastException  if {@code value} is not an instance of {@code CheckoxTreeNode}
+     * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(JTree, Object, boolean, boolean, boolean, int, boolean)
+     * @return the {@code Component} that the renderer uses to draw the value
+	 */
+	@Override
+	public Component getTreeCellRendererComponent(JTree tree,
+            Object value,
+            boolean selected,
+            boolean expanded,
+            boolean leaf,
+            int row,
+            boolean hasFocus){
+		
+		CheckBoxTreeNode treeNode = (CheckBoxTreeNode)value;  
+		if(!treeNode.isSelectable())
+			return super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+
+		checkBox.setSelected(treeNode.isSelected());
+		checkBox.setText(value.toString());
+		if(selected){
+			checkBox.setBackground(getBackgroundSelectionColor());
+			checkBox.setBorderPainted(true);
+		}else{
+			checkBox.setBackground(getBackgroundNonSelectionColor());
+			checkBox.setBorderPainted(false);
+		}
+		return checkBox;
+	}
+
+	private JCheckBox checkBox;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,115 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.checkboxtree;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+
+/**
+ * A special {@code DefaultMutableTreeNode} that has, in addition, the property of being (or not) 
+ * selected.    
+ *
+ */
+@SuppressWarnings("serial")
+public class CheckBoxTreeNode extends DefaultMutableTreeNode {
+
+	/**
+	 * Construct a new instance of this class.
+	 * 
+	 * @param userObject a user object for this tree node @see javax.swing.tree#DefaultMutableTreeNode
+	 * @param selectable whether or not this node is selectable. A non selectable node is pretty much 
+	 * equivalent to a {@code DefaultMutableTreeNode}. 
+	 */
+	public CheckBoxTreeNode(Object userObject, boolean selectable){
+		super(userObject);
+		this.selectable = selectable;
+		selected = false;
+	}
+	
+	/**
+	 * Returns {@code true} if the node is selected, or {@code false} otherwise. 
+	 * 
+	 * @return {@code true} if the node is selected, or {@code false} otherwise. 
+	 */
+	public boolean isSelected() {
+		return selected;
+	}
+
+	/**
+	 * Makes the node selected or unselected 
+	 * 
+	 * @param selected {@code true} to select the node, {@code false} to unselect it. 
+	 */
+	public void setSelected(boolean selected) {
+		if(selectable){			
+			this.selected = selected;
+		}
+	}
+	
+	/**
+	 * Whether the node is selectable or not. This depends on the value of the 
+	 * {@code selected} parameter passed to the constructor. 
+	 * 
+	 * @return {@code true} if the node is selectable, {@code false} otherwise.
+	 */
+	public boolean isSelectable(){
+		return selectable;
+	}
+	
+	/**
+	 * Returns a string representation of the audio description of this node. 
+	 * The value returned by this method can be passed to a Text-To-Speech synthesizer
+	 * for speech rendering.
+	 * 
+	 * @return the audio description of this node.
+	 */
+	public String spokenText(){
+		if(selectable)
+			return toString()+", "+ (selected ? "checked": "unchecked");
+		else
+			return toString();
+	}
+	
+	/**
+	 * Returns a string representation of the path of this tree node. The the string is made up
+	 * as the concatenation of the tree node names from the root to this node, separated by
+	 * {@link #STRING_PATH_SEPARATOR} 
+	 * 
+	 * @return the node path as a String 
+	 */
+	public String getPathAsString(){
+		StringBuilder builder = new StringBuilder();
+		TreeNode [] path = getPath();
+		for(int i=0; i<path.length; i++){
+			builder.append(path[i].toString());
+			if(i != path.length-1)
+				builder.append(STRING_PATH_SEPARATOR);
+		}
+		return builder.toString();
+	}
+	
+	private boolean selected;
+	private boolean selectable;
+	/**
+	 * The character used as a separator when returning the string representation of the path
+	 * of this node via {@link #getPathAsString()}.  
+	 */
+	public static final char STRING_PATH_SEPARATOR = '.';
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/SetProperties.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,206 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.checkboxtree;
+
+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.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * The {@code SetProperties} class represents a persistent set of properties. 
+ * The {@code SetProperties} can be saved to a stream or loaded from a stream. 
+ * 
+ * Unlike {@code java.util.Properties}, this class is not backed by a key-value map, but rather
+ * by a {@code Set<String>}. Therefore it only contains values. All the methods of this class are thread-safe,
+ * but {@code iterator()}. In order to safely iterate on the Set, the iteration must happen in a block syncronized
+ * on the Object returned by {@link #getMonitor()}. 
+ * 
+ * For a description of the methods of the {@code Set} interface, see {@code java.util.Set}
+ * 
+ * @see java.util.Properties
+ * @see java.util.Collections#synchronizedSet(Set)
+ */
+public class SetProperties implements Set<String> {
+	public SetProperties() { 
+		delegate = Collections.synchronizedSet(new HashSet<String>());
+	}
+
+	public SetProperties(Collection<? extends String> collection) {
+		delegate = Collections.synchronizedSet(new HashSet<String>(collection));
+	}
+
+	public SetProperties(int initialCapacity) {
+		delegate = Collections.synchronizedSet(new HashSet<String>(initialCapacity));
+	}
+	
+	public SetProperties(int initialCapacity, float loadFactor){
+		delegate = Collections.synchronizedSet(new HashSet<String>(initialCapacity, loadFactor));
+	}
+
+	/* DELEGATE METHODS */
+	@Override
+	public boolean add(String arg0) {
+		return delegate.add(arg0);
+	}
+
+	@Override
+	public boolean addAll(Collection<? extends String> arg0) {
+		return delegate.addAll(arg0);
+	}
+
+	@Override
+	public void clear() {
+		delegate.clear();
+	}
+
+	@Override
+	public boolean contains(Object arg0) {
+		return delegate.contains(arg0);
+	}
+
+	@Override
+	public boolean containsAll(Collection<?> arg0) {
+		return delegate.containsAll(arg0);
+	}
+
+	@Override
+	public boolean equals(Object arg0) {
+		return delegate.equals(arg0);
+	}
+
+	@Override
+	public int hashCode() {
+		return delegate.hashCode();
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return delegate.isEmpty();
+	}
+
+	@Override	
+	public Iterator<String> iterator() {
+		return delegate.iterator();
+	}
+
+	@Override
+	public boolean remove(Object arg0) {
+		return delegate.remove(arg0);
+	}
+
+	@Override
+	public boolean removeAll(Collection<?> arg0) {
+		return delegate.removeAll(arg0);
+	}
+
+	@Override
+	public boolean retainAll(Collection<?> arg0) {
+		return delegate.retainAll(arg0);
+	}
+
+	@Override
+	public int size() {
+		return delegate.size();
+	}
+
+	@Override
+	public Object[] toArray() {
+		return delegate.toArray();
+	}
+
+	@Override
+	public <T> T[] toArray(T[] arg0) {
+		return delegate.toArray(arg0);
+	}
+
+	/**
+	 * Stores the content of this set (strings) in a text file. The content can then be retrieved 
+	 * by calling {@code load()} passing as argument the same file as this method. The strings 
+	 * will be written on a row each. 
+	 * 
+	 * @param file A valid File where the content of this object will be stored  
+	 * @param comments A comment string that will be added at the beginning of the file. 
+	 *         
+	 * @throws IOException if an exception occurs while writing the file
+	 */
+	public void store(File file, String comments) throws IOException{
+		synchronized(delegate){
+			if(file == null)
+				throw new IllegalArgumentException("File cannot be null");
+			FileWriter fWriter = new FileWriter(file);
+			BufferedWriter writer = new BufferedWriter(fWriter);
+			if(comments != null){
+				writer.write(COMMENTS_ESCAPE+" "+comments);
+				writer.newLine();
+				writer.newLine();
+			}
+				
+			for(String property : this){
+				writer.write(property);
+				writer.newLine();
+			}
+			writer.close();
+		}
+	}
+	
+	/**
+	 * Loads the content of a file into this set. Whaen the file is read, each row is taken as an
+	 * entry of the set.   
+	 *  
+	 * @param file the file where to read the entries from
+	 * @throws IOException if an exception occurs while reading the file 
+	 */
+	public void load(File file) throws IOException{
+		synchronized(delegate){
+			if(file == null)
+				throw new IllegalArgumentException("File cannot be null");
+			FileReader fReader = new FileReader(file);
+			BufferedReader reader = new BufferedReader(fReader);
+			String line;
+			while((line = reader.readLine()) != null){
+				if(!line.isEmpty() && !line.trim().startsWith(COMMENTS_ESCAPE))
+					add(line);
+			}
+			reader.close();
+		}
+	}
+	
+	/**
+	 * Returns the Object all the methods (but {@code iterator()} of this {@code Set} are synchronized on. It can be used 
+	 * to safely iterate on this object without incurring in a race condition   
+	 *  
+	 * @return an {@code Object} to be used as synchronization monitor  
+	 * 
+	 * @see java.util.Collections#synchronizedSet(Set) 
+	 */
+	public Object getMonitor(){
+		return delegate;
+	}
+	
+	private Set<String> delegate;
+	private static String COMMENTS_ESCAPE = "#";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/XMLHandler.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,106 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.checkboxtree;
+
+import java.util.Stack;
+
+import javax.swing.tree.DefaultTreeModel;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/* Build a tree out of an XML file. The hierarchical structure of the XML file reflects 
+ * the structure of the tree. The XMl file must have the form specified in CheckBoxTree
+ * class comment.  
+ *
+ */
+class XMLHandler extends DefaultHandler {
+	
+	@SuppressWarnings("serial")
+	public XMLHandler(DefaultTreeModel treeModel, SetProperties properties){
+		this.properties = properties;
+		this.treeModel = treeModel;
+		/* path is used to keep track of the current node's path. If the path is present as a          *      
+		 * properties entry, then the current node, that has jus been created, will be set as selected */
+		path = new Stack<String>() {
+			@Override
+			public String toString(){
+				StringBuilder builder = new StringBuilder();
+				for(int i=0; i<size();i++){
+					builder.append(get(i));
+					if(i != size()-1)
+						builder.append(CheckBoxTreeNode.STRING_PATH_SEPARATOR);
+				}
+				return builder.toString();
+			}
+		};
+	}
+
+	/*
+	 * Create a CheckBoxTreeNode out of an xml tag. The tree node name is given by the value 
+	 * attribute. Whether the tree node is selectable or not, depends on the tag name, which can be 
+	 * selectable/unselectable. If the node is selectable, then it will be set as selected if its path 
+	 * is present in the properties  
+	 */
+	@Override
+	public void startElement(String uri,
+            String localName,
+            String qName,
+            Attributes attributes)
+     throws SAXException {
+		String nodeName = attributes.getValue(VALUE_ATTR);
+		if(nodeName == null)
+			throw new SAXException("Value attribute missing");
+		boolean isSelectable = SELECTABLE_NODE.equals(qName);
+		
+		CheckBoxTreeNode newNode = new CheckBoxTreeNode(nodeName,isSelectable);
+		if(currentNode == null){
+			currentNode = newNode;
+			treeModel.setRoot(newNode);
+		}else{
+			currentNode.add(newNode);
+		}
+		currentNode = newNode;
+		path.push(nodeName);
+		if(properties.contains(path.toString()))
+			newNode.setSelected(true);
+	}
+	
+	/* when an end tag is encountered, we carry on building the tree with the father as current node */
+	@Override
+	public void endElement(String uri,
+            String localName,
+            String qName)
+     throws SAXException {
+		path.pop();
+		currentNode = (CheckBoxTreeNode)currentNode.getParent();
+	}
+
+	private CheckBoxTreeNode currentNode;
+	private DefaultTreeModel treeModel;
+	private SetProperties properties;
+	private Stack<String> path;
+	/* attributes used in the XML file */
+	public static final String VALUE_ATTR = "value";
+	public static final String SELECTABLE_NODE = "selectable";
+	public static final String UNSELECTABLE_NODE = "selectable";
+	
+	
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionListener.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionListener.java	Wed Apr 25 17:09:09 2012 +0100
@@ -26,9 +26,40 @@
  */
 public interface CollectionListener {
 	
+	/**
+	 * Called when either a node or an edge is inserted in the model.
+	 * @param e an object representing the insertion event 
+	 */
 	void elementInserted(CollectionEvent e);
 	
+	/**
+	 * Called when either a node or an edge is removed in the model.
+	 * @param e an object representing the remotion event 
+	 */
 	void elementTakenOut(CollectionEvent e);
 	
+	/**
+	 * Called when either a node or an edge in the model this listener is registered on.
+	 * In order to identify which part of the element has been changed the call {@code e.getChangeType()}
+	 * can be used. Furthermore the call {@code e.getArguments()} can return an object 
+	 * with additional information related to the change.
+	 * The string returned by {@code e.getChangeType()} can be one of the following : 
+	 *  <ul>
+	 *  <li>{@code name} : when the name of an element is changed
+	 *  <li>{@code properties} : when the all the properties of a node are changed all at once
+	 *  <li>{@code properties.clear} : when the all the properties of a node are deleted all at once
+	 *  <li>{@code property.add} : when a new property is added to a node. {@code e.getArguments()} will return 
+	 *  a {@code PropertyChangeArgs} object, to retrieve the property from the node 
+	 *  <li>{@code property.set} : when a property value changes into another value. {@code e.getArguments()} will return 
+	 *  a {@code PropertyChangeArgs} object, to retrieve the property from the node  
+	 *  <li>{@code property.remove} : when a property is removed from a node. 
+	 *  <li>{@code property.modifiers} : when modifiers of a property are changed. {@code e.getArguments()} will return 
+	 *  a {@code PropertyChangeArgs} object to retrieve the property from the node. {@code getOldValue()} will return in this 
+	 *  case a String concatenation of all the modifier that were set before the change for this property.
+	 *  <li>{@code arrowHead} : when the arrow head of an edge is changed.
+	 *  <li>{@code endLabel} :
+	 *  </ul>
+	 * @param e an object representing the change event
+	 */
 	void elementChanged(ElementChangedEvent e);
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionModel.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionModel.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,6 +19,7 @@
 package uk.ac.qmul.eecs.ccmi.diagrammodel;
 
 import java.util.Collection;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.swing.event.ChangeListener;
 
@@ -44,40 +45,49 @@
 	/**
 	 * insert a DiagramNode into the diagram model
 	 * @param n the DiagramNode to be inserted in the collection
+	 * @param source the source of the action. This will be reported as the source of the event 
+	 * generated by this action to the registered listeners. If null the CollectionModel instance
+	 * itself will be used as source 
 	 * @return true if this collection changed as a result of the call
 	 */
-	boolean insert(N n) ;
+	boolean insert(N n, Object source) ;
 	
 	/**
 	 * insert a DiagramEdge into the diagram model
 	 * @param e the DiagramNode to be inserted in the collection
+	 * @param source the source of the action. This will be reported as the source of the event 
+	 * generated by this action to the registered listeners. If null the CollectionModel instance
+	 * itself will be used as source
 	 * @return true if this collection changed as a result of the call
 	 */
-	boolean insert(E e);
+	boolean insert(E e, Object source);
 	
 	/**
 	 * Removes a DiagramElement from the model
 	 * @param e the diagramElement to be removed
+	 * @param source the source of the action. This will be reported as the source of the event 
+	 * generated by this action to the registered listeners. If null the CollectionModel instance
+	 * itself will be used as source
 	 * @return true if this collection changed as a result of the call
 	 */
-	boolean takeOut(DiagramElement e);
+	boolean takeOut(DiagramElement e, Object source);
 	
 	/**
-	 * Returns the diagram nodes contained by the model as a Collection  
+	 * Returns the diagram nodes contained by the model as a collection  
 	 * @return the collection of diagram nodes
 	 */
 	Collection<N> getNodes();
 	
 	/**
-	 * Returns the diagram edges contained by the model as a Collection  
+	 * Returns the diagram edges contained by the model as a collection  
 	 * @return the collection of diagram edges
 	 */
 	Collection<E> getEdges();
 	
 	/**
 	 * return a list of nodes and edges in the model as a unique collection 
-	 * of Diagram elements. 
-	 * @return
+	 * of diagram elements. 
+	 * @return the collection of diagram elements
 	 */
 	Collection<DiagramElement> getElements();
 	
@@ -116,11 +126,10 @@
 	void sort();
 	
 	/**
-	 * Returns an object that can be used to access the nodes and edges (via {@link #getNodes()}
-	 * and {@link #getEdges()} in a synchronized block. The monitor is guaranteed to give 
-	 * exclusive access even in regards to the access via the tree side of the model.  
-	 * @return
+	 * Returns a reentrant lock that can be used to access the nodes and edges via {@code getNodes()}
+	 * and {@code getEdges()} and the change methods in a synchronized fashion.   
+	 * @return a lock object
 	 */
-	Object getMonitor();
+	ReentrantLock getMonitor();
 
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramEdge.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramEdge.java	Wed Apr 25 17:09:09 2012 +0100
@@ -54,7 +54,7 @@
 		b.append(' ').append(spokenText());
 		b.append(". Connecting ");
 		for(int i=0; i<getChildCount();i++){
-			b.append(((DiagramModelTreeNode)getChildAt(i)).getName());
+			b.append(((DiagramTreeNode)getChildAt(i)).getName());
 			b.append(and);
 		}
 		// remove the last " and "
@@ -69,11 +69,11 @@
 	 * @param n the node,at whose end the label is located 
 	 * @param label the label
 	 */
-	public void setEndLabel(DiagramNode n, String label){
+	public void setEndLabel(DiagramNode n, String label, Object source){
 		if(label == null)
 			label = "";
 		endLabels.put(n, label);
-		notifyChange(new ElementChangedEvent(n,this,"endLabel"));
+		notifyChange(new ElementChangedEvent(this,n,"endLabel",source));
 	}
 
 	/**
@@ -81,7 +81,6 @@
 	 * the label would be put in proximity of the node.
 	 * 
 	 * @param n the node,at whose end the label is located 
-	 * @param label the label
 	 */
 	public String getEndLabel(DiagramNode n){
 		String s = endLabels.get(n);
@@ -110,9 +109,9 @@
 	 * as the empty string.
 	 * 
 	 */
-	public void setEndDescription(DiagramNode n, int index){
+	public void setEndDescription(DiagramNode n, int index, Object source){
 		endDescriptions.put(n, index);
-		notifyChange(new ElementChangedEvent(n,this,"arrowHead"));
+		notifyChange(new ElementChangedEvent(this,n,"arrowHead",source));
 	}
 
 	/**
@@ -159,7 +158,7 @@
 	public abstract void connect(List<DiagramNode> nodes) throws ConnectNodesException;
 
 	/**
-	 * An index to be passed to {@link #setEndDescription(DiagramNode, int)} in order 
+	 * An index to be passed to {@link #setEndDescription(DiagramNode, int, Object)} in order 
 	 * to set the edge with no description at the end related to that diagram node  
 	 */
 	public static int NO_END_DESCRIPTION_INDEX = -1;
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramElement.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramElement.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,6 +19,8 @@
 package uk.ac.qmul.eecs.ccmi.diagrammodel;
 
 import java.io.InputStream;
+import java.util.concurrent.locks.ReentrantLock;
+
 
 /**
  * A Diagram Element is either a node or an edge of the diagram. It's an abstract 
@@ -26,7 +28,7 @@
  *
  */
 @SuppressWarnings("serial")
-public abstract class DiagramElement extends DiagramModelTreeNode implements Cloneable{
+public abstract class DiagramElement extends DiagramTreeNode implements Cloneable{
 	
 	protected DiagramElement(){
 		name = "";
@@ -35,9 +37,10 @@
 	}
 	
 	/**
-	 * Returns the type of this diagram element. The type is the same for all the elements 
-	 * which fall under the same type, whereas a different name can be assigned to each
-	 * instance of such elements.  
+	 * Returns the type of this diagram element. The type is like the category this element belongs to. 
+	 * For instance in a public transport diagram one might have three types of diagram element: tube, train
+	 * and busses.   
+	 *  
 	 * @return the type of this element 
 	 */
 	public String getType(){
@@ -47,6 +50,7 @@
 	/**
 	 * Set the type of this diagram element. This method should be called as soon as the object is created
 	 * and should not be called anymore on this object.
+	 * 
 	 * @param type the type of this element
 	 */
 	protected void setType(String type){
@@ -76,9 +80,10 @@
 	/**
 	 * Sets the name of this element instance.
 	 * 
-	 * @param the string to set as the name 
+	 * @param s the string to set as the name 
+	 * @param source the source of this action
 	 */
-	public void setName(String s){
+	public void setName(String s, Object source){
 		String name = s;
 		/* if the user enters an empty string we go back to the default name */
 		if(s.isEmpty() && id != NO_ID){
@@ -86,7 +91,7 @@
 		}		
 		setUserObject(name);
 		this.name = name;
-		notifyChange(new ElementChangedEvent(this,this,"name"));
+		notifyChange(new ElementChangedEvent(this,this,"name",source));
 	}
 	
 	/**
@@ -122,13 +127,19 @@
 	public long getId(){
 		return id;
 	}
+	
+	public ReentrantLock getMonitor(){
+		if(notifier == null)
+			return null;
+		return (ReentrantLock)notifier;
+	}
 		
 	/** 
 	 *  Sets the notifier to be used for notification 
 	 *  following an internal change of the node
 	 * @param notifier the notifier call the notify method(s) on 
 	 */
-	public void setNotifier(ElementNotifier notifier){
+	<N extends DiagramNode,E extends DiagramEdge> void setNotifier(DiagramModel<N,E>.ReentrantLockNotifier notifier){
 		this.notifier = notifier;
 	}
 	
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java	Wed Apr 25 17:09:09 2012 +0100
@@ -1,6 +1,6 @@
 /*  
  CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-  
+
  Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
 
  This program is free software: you can redistribute it and/or modify
@@ -15,7 +15,7 @@
 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+ */
 package uk.ac.qmul.eecs.ccmi.diagrammodel;
 
 import java.util.ArrayList;
@@ -28,6 +28,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
@@ -37,7 +38,6 @@
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
-import uk.ac.qmul.eecs.ccmi.utils.Pair;
 
 /**
  * This class represent a model as per in the model-view control architecture. 
@@ -63,42 +63,36 @@
 	 */
 	@SuppressWarnings("serial")
 	public DiagramModel(N [] nodePrototypes, E [] edgePrototypes) {
-		root = new DiagramModelTreeNode(ROOT_LABEL){
+		root = new DiagramTreeNode(ROOT_LABEL){
 			@Override
 			public boolean isRoot(){
 				return true;
 			}
 		};
 		modified = false;
-		
+
 		nodeCounter = 0;
 		edgeCounter = 0;
-		
+
+		notifier = new ReentrantLockNotifier();
+
 		treeModel = new InnerTreeModel(root);
 		treeModel.setEventSource(treeModel);/* default event source is the tree itself */
 		diagramCollection = new InnerDiagramCollection();
-		
+
 		nodes = new ArrayList<N>(INITIAL_NODES_SIZE);
 		edges = new ArrayList<E>(INITIAL_EDGES_SIZE);
 		elements = new ArrayList<DiagramElement>(INITIAL_NODES_SIZE+INITIAL_EDGES_SIZE);
-		
+
 		changeListeners = new LinkedList<ChangeListener>();
-		
-		notifier = new ElementNotifier(){
-			@Override
-			public void notifyChange(ElementChangedEvent evt) {
-				_change(evt);
-				triggerModification(evt.getDiagramElement());
-			}
-		};
-		
+
 		for(N n : nodePrototypes)
 			addType(n);
 		for(E e : edgePrototypes){
 			addType(e);
 		}
 	}
-	
+
 	/**
 	 * Returns a CollectionModel for this diagram
 	 * 
@@ -107,7 +101,7 @@
 	public CollectionModel<N,E> getDiagramCollection(){
 		return diagramCollection;
 	}
-	
+
 	/**
 	 * Returns a TreeModel for this diagram
 	 * 
@@ -116,58 +110,64 @@
 	public TreeModel<N,E> getTreeModel(){
 		return treeModel;
 	}	
-	
-	private void triggerModification(Object source){
+
+	private void handleChangeListeners(Object source){
 		if(modified) // fire the listener only the first time a change happens
 			return;
 		modified = true;
 		fireChangeListeners(source);
 	}
-	
+
 	private void addChangeListener(ChangeListener l){
 		changeListeners.add(l);
 	}
-	
+
 	private void removeChangeListener(ChangeListener l){
 		changeListeners.remove(l);
 	}
-	
+
 	protected void fireChangeListeners(Object source){
 		ChangeEvent changeEvent = new ChangeEvent(source);
 		for(ChangeListener l : changeListeners)
 			l.stateChanged(changeEvent);
 	}
-	
+
 	private void addType(DiagramElement element){
-		DiagramModelTreeNode typeNode = _lookForChild(root, element.getType());
+		DiagramTreeNode typeNode = _lookForChild(root, element.getType());
 		if(typeNode == null){
 			typeNode = new TypeMutableTreeNode(element);
 			treeModel.insertNodeInto(typeNode, root, root.getChildCount());
 		}
 	}
-	
+
 	private class InnerDiagramCollection implements CollectionModel<N,E> {
 
 		public InnerDiagramCollection(){
 			listeners = new ArrayList<CollectionListener>();
 		}
-		
+
 		@Override
-		public boolean insert(N n){
-			return _insert(n,this);
+		public boolean insert(N n, Object source){
+			if(source == null)
+				source = this;
+			return _insert(n,source);
 		}
-		
+
 		@Override
-		public boolean insert(E e){
-			return _insert(e,this);
+		public boolean insert(E e, Object source){
+			if(source == null)
+				source = this;
+			return _insert(e,source);
 		}
-		
+
 		@Override
-		public boolean takeOut(DiagramElement element){
+		public boolean takeOut(DiagramElement element, Object source){
+			if(source == null)
+				source = this;
 			if(element instanceof DiagramNode)
-				return _takeOut((DiagramNode)element,this);
+				return _takeOut((DiagramNode)element,source);
 			if(element instanceof DiagramEdge)
-				return _takeOut((DiagramEdge)element,this);
+				return _takeOut((DiagramEdge)element,source);
 			return false;
 		}
 
@@ -180,19 +180,19 @@
 		public void removeCollectionListener(CollectionListener listener) {
 			listeners.remove(listener);
 		}
-		
+
 		protected void fireElementInserted(Object source, DiagramElement element) {
 			for(CollectionListener l : listeners){
 				l.elementInserted(new CollectionEvent(source,element));
 			}
 		}
-		
+
 		protected void fireElementTakenOut(Object source, DiagramElement element) {
 			for(CollectionListener l : listeners){
 				l.elementTakenOut(new CollectionEvent(source,element));
 			}
 		}
-		
+
 		protected void fireElementChanged(ElementChangedEvent evt){
 			for(CollectionListener l : listeners){
 				l.elementChanged(evt);
@@ -208,71 +208,78 @@
 		public Collection<E> getEdges() {
 			return Collections.unmodifiableCollection(edges);
 		}
-		
+
 		@Override
 		public Collection<DiagramElement> getElements(){
 			return Collections.unmodifiableCollection(elements);
 		}
 
 		@Override
-		public Object getMonitor(){
-			return DiagramModel.this;
+		public ReentrantLock getMonitor(){
+			return notifier;
 		}
-		
+
 		@Override
 		public void addChangeListener(ChangeListener l){
 			DiagramModel.this.addChangeListener(l);
 		}
-		
+
 		@Override
 		public void removeChangeListener(ChangeListener l){
 			DiagramModel.this.removeChangeListener(l);
 		}
-		
+
 		/* sort the collections according to the id of nodes */
 		public void sort(){
 			Collections.sort(nodes, DiagramElementComparator.getInstance());
 			Collections.sort(edges, DiagramElementComparator.getInstance());
 		}
-		
+
 		public boolean isModified(){
 			return modified;
 		}
-		
+
 		public void setUnmodified(){
-	    	modified = false;
-	    }
-		
+			modified = false;
+		}
+
 		protected ArrayList<CollectionListener> listeners;
 
 	}
-    
-    @SuppressWarnings("serial")
+
+	@SuppressWarnings("serial")
 	private class InnerTreeModel extends DefaultTreeModel implements TreeModel<N,E>{
 
-    	public InnerTreeModel(TreeNode root){
-    		super(root);
-    		bookmarks = new LinkedHashMap<String, DiagramModelTreeNode>();
-    	}
-    	
-		@Override
-		public boolean insertTreeNode(N treeNode){
-			return _insert(treeNode,this);
-		}
-		
-		@Override
-		public boolean insertTreeNode(E treeNode){
-			return _insert(treeNode,this);
+		public InnerTreeModel(TreeNode root){
+			super(root);
+			bookmarks = new LinkedHashMap<String, DiagramTreeNode>();
+			diagramTreeNodeListeners = new ArrayList<DiagramTreeNodeListener>();
 		}
 
 		@Override
-		public boolean takeTreeNodeOut(DiagramElement treeNode){
+		public boolean insertTreeNode(N treeNode, Object source){
+			if(source == null)
+				source = this;
+			return _insert(treeNode,source);
+		}
+
+		@Override
+		public boolean insertTreeNode(E treeNode, Object source){
+			if(source == null)
+				source = this;
+			return _insert(treeNode,source);
+		}
+
+		@Override
+		public boolean takeTreeNodeOut(DiagramElement treeNode, Object source){
+			if(source == null)
+				source = this;
 			boolean result;
 			if(treeNode instanceof DiagramEdge){
-				result = _takeOut((DiagramEdge)treeNode,this);
+				result = _takeOut((DiagramEdge)treeNode,source);
 			}
 			else{
-				result = _takeOut((DiagramNode)treeNode,this);
+				result = _takeOut((DiagramNode)treeNode,source);
 			}
 			/* remove the bookmarks associated with the just deleted diagram element, if any */
 			for(String key : treeNode.getBookmarkKeys())
@@ -281,67 +288,86 @@
 		}
 
 		@Override
-		public DiagramModelTreeNode putBookmark(String bookmark, DiagramModelTreeNode treeNode){
+		public DiagramTreeNode putBookmark(String bookmark, DiagramTreeNode treeNode, Object source){
 			if(bookmark == null)
 				throw new IllegalArgumentException("bookmark cannot be null");
-			setEventSource(this);
+			if(source == null)
+				source = this;
+			setEventSource(source);
 			treeNode.addBookmarkKey(bookmark);
-			DiagramModelTreeNode result =  bookmarks.put(bookmark, treeNode);
+			DiagramTreeNode result =  bookmarks.put(bookmark, treeNode);
 			nodeChanged(treeNode);
 			iLog("bookmark added",bookmark);
-			triggerModification(this);
+			DiagramTreeNodeEvent evt = new DiagramTreeNodeEvent(treeNode,bookmark,source);
+			for(DiagramTreeNodeListener l : diagramTreeNodeListeners){
+				l.bookmarkAdded(evt);
+			}
+			handleChangeListeners(this);
 			return result;
 		}
 
 		@Override
-		public DiagramModelTreeNode getBookmarkedTreeNode(String bookmark) {
+		public DiagramTreeNode getBookmarkedTreeNode(String bookmark) {
 			return bookmarks.get(bookmark);
 		}
-		
+
 		@Override
-		public DiagramModelTreeNode removeBookmark(String bookmark) {
-			setEventSource(this);
-			DiagramModelTreeNode treeNode = bookmarks.remove(bookmark);
+		public DiagramTreeNode removeBookmark(String bookmark,Object source) {
+			if(source == null)
+				source = this;
+			setEventSource(source);
+			DiagramTreeNode treeNode = bookmarks.remove(bookmark);
 			treeNode.removeBookmarkKey(bookmark);
 			nodeChanged(treeNode);
 			iLog("bookmark removed",bookmark);
-			triggerModification(this);
+			DiagramTreeNodeEvent evt = new DiagramTreeNodeEvent(treeNode,bookmark,source);
+			for(DiagramTreeNodeListener l : diagramTreeNodeListeners){
+				l.bookmarkRemoved(evt);
+			}
+			handleChangeListeners(this);
 			return treeNode;
 		}
-		
+
 		@Override
 		public Set<String> getBookmarks(){
 			return new LinkedHashSet<String>(bookmarks.keySet());
 		}
-		
+
 		@Override
-		public void setNotes(DiagramModelTreeNode treeNode, String notes){
-			setEventSource(this);
-			treeNode.setNotes(notes);
+		public void setNotes(DiagramTreeNode treeNode, String notes,Object source){
+			if(source == null)
+				source = this;
+			setEventSource(source);
+			String oldValue = treeNode.getNotes();
+			treeNode.setNotes(notes,source);
 			nodeChanged(treeNode);
 			iLog("notes set for "+treeNode.getName(),"".equals(notes) ? "empty notes" : notes.replaceAll("\n", "\\\\n"));
-			triggerModification(this);
+			DiagramTreeNodeEvent evt = new DiagramTreeNodeEvent(treeNode,oldValue,source);
+			for(DiagramTreeNodeListener l : diagramTreeNodeListeners){
+				l.notesChanged(evt);
+			}
+			handleChangeListeners(source);
 		}
-		
+
 		private void setEventSource(Object source){
 			this.src = source;
 		}
-		
+
 		@Override
-		public Object getMonitor(){
-			return DiagramModel.this;
+		public ReentrantLock getMonitor(){
+			return notifier;
 		}
-		
+
 		@Override
-		public void addChangeListener(ChangeListener l){
-			DiagramModel.this.addChangeListener(l);
+		public void addDiagramTreeNodeListener(DiagramTreeNodeListener l){
+			diagramTreeNodeListeners.add(l);
 		}
-		
+
 		@Override
-		public void removeChangeListener(ChangeListener l){
-			DiagramModel.this.removeChangeListener(l);
+		public void removeDiagramTreeNodeListener(DiagramTreeNodeListener l){
+			diagramTreeNodeListeners.remove(l);
 		}
-		
+
 		/* redefine the fire methods so that they set the source object according */
 		/* to whether the element was inserted from the graph or from the tree    */
 		@Override
@@ -367,50 +393,60 @@
 				int[] childIndices, Object[] children) {
 			super.fireTreeStructureChanged(src, path, childIndices, children);
 		}
-		
+
 		public boolean isModified(){
 			return modified;
 		}
-		
+
 		public void setUnmodified(){
-	    	modified = false;
-	    }
-		
+			modified = false;
+		}
+
 		private Object src;
-		private Map<String, DiagramModelTreeNode> bookmarks;		
-    }
-    
-    private synchronized boolean _insert(N n, Object source) {
-    	assert(n != null);
+		private Map<String, DiagramTreeNode> bookmarks;
+		private ArrayList<DiagramTreeNodeListener> diagramTreeNodeListeners; 
+	}
+	
+	@SuppressWarnings("serial")
+	class ReentrantLockNotifier extends ReentrantLock implements ElementNotifier {
+		@Override
+		public void notifyChange(ElementChangedEvent evt) {
+			_change(evt);
+			handleChangeListeners(evt.getDiagramElement());
+		}
+	}
 
-    	/* if id has already been given then sync the counter so that a surely new value is given to the next nodes */
-    	if(n.getId() == DiagramElement.NO_ID)
-    		n.setId(++nodeCounter);
-    	else if(n.getId() > nodeCounter)
-    		nodeCounter = n.getId();
-    	
-    	treeModel.setEventSource(source);
+	private boolean _insert(N n, Object source) {
+		assert(n != null);
+
+		/* if id has already been given then sync the counter so that a surely new value is given to the next nodes */
+		if(n.getId() == DiagramElement.NO_ID)
+			n.setId(++nodeCounter);
+		else if(n.getId() > nodeCounter)
+			nodeCounter = n.getId();
+
+		treeModel.setEventSource(source);
 		nodes.add(n);	
 		elements.add(n);
 		/* add the node to outer node's (if any) inner nodes */
 		if(n.getExternalNode() != null)
 			n.getExternalNode().addInternalNode(n);
-		
+
 		/* decide where to insert the node based on whether this is an inner node or not */
 		MutableTreeNode parent;
 		if(n.getExternalNode() == null){
-			DiagramModelTreeNode typeNode = _lookForChild(root, n.getType());
+			DiagramTreeNode typeNode = _lookForChild(root, n.getType());
 			if(typeNode == null)
 				throw new IllegalArgumentException("Node type "+n.getType()+" not present in the model");
 			parent = typeNode;
 		}else{
 			parent = n.getExternalNode();
 		}
-		
+
 		/* add to the node one child per property type */
 		for(String propertyType : n.getProperties().getTypes())
 			n.insert(new PropertyTypeMutableTreeNode(propertyType,n), n.getChildCount());
-		
+
 		/* inject the notifier for managing changes internal to the edge */
 		n.setNotifier(notifier);
 
@@ -419,15 +455,15 @@
 		/* this is necessary to increment the child counter displayed between brackets */
 		treeModel.nodeChanged(parent);
 		diagramCollection.fireElementInserted(source,n);
-		triggerModification(n);
-		
+		handleChangeListeners(n);
+
 		iLog("node inserted",DiagramElement.toLogString(n));
 		return true;
-    }
-    
-    private synchronized boolean _takeOut(DiagramNode n, Object source) {
-    	treeModel.setEventSource(source);
-    	/* recursively remove internal nodes of this node */
+	}
+
+	private boolean _takeOut(DiagramNode n, Object source) {
+		treeModel.setEventSource(source);
+		/* recursively remove internal nodes of this node */
 		_removeInternalNodes(n,source);
 		/* clear external node and clear edges attached to this node and updates other ends of such edges */
 		_clearNodeReferences(n,source);
@@ -440,8 +476,8 @@
 		elements.remove(n);
 		/* notify all the listeners a new node has been removed */
 		diagramCollection.fireElementTakenOut(source,n);
-		triggerModification(n);
-		
+		handleChangeListeners(n);
+
 		if(nodes.isEmpty()){
 			nodeCounter = 0;
 		}else{
@@ -451,125 +487,125 @@
 		}
 		iLog("node removed",DiagramElement.toLogString(n));
 		return true;
-    }
-    
-    private  synchronized boolean _insert(E e, Object source)  {
-    	assert(e != null);
-    	/* executes formal controls over the edge's node, which must be specified from the outer class*/
-    	if(e.getNodesNum() < 2)
-    		throw new MalformedEdgeException(e,"too few (" +e.getNodesNum()+ ") nodes");
-    	
-    	/* if id has already been given then sync the counter so that a surely new value is given to the next edges */
-    	if(e.getId() > edgeCounter)
-    		edgeCounter = e.getId();
-    	else
-    		e.setId(++edgeCounter);
-    	
-    	treeModel.setEventSource(source);
-    	edges.add(e);
-    	elements.add(e);
-    	
-    	/* updates the nodes' edge reference and the edge tree references */ 
+	}
+
+	private boolean _insert(E e, Object source)  {
+		assert(e != null);
+		/* executes formal controls over the edge's node, which must be specified from the outer class*/
+		if(e.getNodesNum() < 2)
+			throw new MalformedEdgeException("too few (" +e.getNodesNum()+ ") nodes");
+
+		/* if id has already been given then sync the counter so that a surely new value is given to the next edges */
+		if(e.getId() > edgeCounter)
+			edgeCounter = e.getId();
+		else
+			e.setId(++edgeCounter);
+
+		treeModel.setEventSource(source);
+		edges.add(e);
+		elements.add(e);
+
+		/* updates the nodes' edge reference and the edge tree references */ 
 		for(int i = e.getNodesNum()-1; i >= 0; i--){
 			DiagramNode n = e.getNodeAt(i);
 			assert(n != null);
 			/* insert first the type of the edge, if not already present */
-			DiagramModelTreeNode edgeType = _lookForChild(n, e.getType());
+			DiagramTreeNode edgeType = _lookForChild(n, e.getType());
 			if(edgeType == null){
 				edgeType = new EdgeReferenceHolderMutableTreeNode(e.getType());
 				treeModel.insertNodeInto(edgeType, n, 0);
 			}
-			
-			/* insert the edge reference under its type tree node, in the Node*/
+
+			/* insert the edge reference under its type tree node, in the  node*/
 			treeModel.insertNodeInto(new EdgeReferenceMutableTreeNode(e,n), edgeType, 0);
 			/* this is necessary to increment the child counter displayed between brackets */
-    		treeModel.nodeChanged(edgeType);
+			treeModel.nodeChanged(edgeType);
 
 			n.addEdge(e);
 			/* insert the node reference into the edge tree node */
 			e.insert(new NodeReferenceMutableTreeNode(n,e), 0);
 		}
-		
-		DiagramModelTreeNode parent = _lookForChild(root, e.getType());
+
+		DiagramTreeNode parent = _lookForChild(root, e.getType());
 		if(parent == null)
 			throw new IllegalArgumentException("Edge type "+e.getType()+" not present in the model");
-	
+
 		/* inject the controller and notifier to manage changes internal to the edge */
 		e.setNotifier(notifier);
-		
+
 		/* c'mon baby light my fire */
 		treeModel.insertNodeInto(e, parent, parent.getChildCount());
 		/* this is necessary to increment the child counter displayed between brackets */
 		treeModel.nodeChanged(parent);
 		diagramCollection.fireElementInserted(source,e);
-		triggerModification(e);
-		
+		handleChangeListeners(e);
+
 		StringBuilder builder = new StringBuilder(DiagramElement.toLogString(e));
 		builder.append(" connecting:");
 		for(int i=0; i<e.getNodesNum();i++)
 			builder.append(DiagramElement.toLogString(e.getNodeAt(i))).append(' ');
-		
+
 		iLog("edge inserted",builder.toString());
 		return true;
-    }
-    
-    private synchronized boolean _takeOut(DiagramEdge e, Object source) {
-    	treeModel.setEventSource(source);
+	}
+
+	private boolean _takeOut(DiagramEdge e, Object source) {
+		treeModel.setEventSource(source);
 		/* update the nodes attached to this edge */
-    	_clearEdgeReferences(e);    		
-    	/* remove the edge from the collection */	
-    	edges.remove(e);
-    	elements.remove(e);
-    	/* remove the edge from the tree (fires tree listeners) */
-    	treeModel.removeNodeFromParent(e);
-    	/* this is necessary to increment the child counter displayed between brackets */
+		_clearEdgeReferences(e);    		
+		/* remove the edge from the collection */	
+		edges.remove(e);
+		elements.remove(e);
+		/* remove the edge from the tree (fires tree listeners) */
+		treeModel.removeNodeFromParent(e);
+		/* this is necessary to increment the child counter displayed between brackets */
 		treeModel.nodeChanged(e.getParent());
-    	/* notify listeners for collection */
-    	diagramCollection.fireElementTakenOut(source,e);
-    	triggerModification(e);
-    	
-    	if(edges.isEmpty()){
-    		edgeCounter = 0;
-    	}else{
-    		long lastEdgeId = edges.get(edges.size()-1).getId(); 
+		/* notify listeners for collection */
+		diagramCollection.fireElementTakenOut(source,e);
+		handleChangeListeners(e);
+
+		if(edges.isEmpty()){
+			edgeCounter = 0;
+		}else{
+			long lastEdgeId = edges.get(edges.size()-1).getId(); 
 			if(e.getId() > lastEdgeId)
 				edgeCounter = lastEdgeId;
-    	}
-    	iLog("edge removed",DiagramElement.toLogString(e));
-    	return true;
-    }
+		}
+		iLog("edge removed",DiagramElement.toLogString(e));
+		return true;
+	}
 
-    private void _removeInternalNodes(DiagramNode n, Object source){
-    	for(int i=0; i<n.getInternalNodesNum(); i++){
-    		DiagramNode innerNode = n.getInternalNodeAt(i);
-    		_clearNodeReferences(innerNode, source);
-    		_removeInternalNodes(innerNode, source);
-    		n.removeInternalNode(innerNode);
-    		nodes.remove(n);
-    	}   	
-    }
-	
-    /* removes both inner and tree node references to an edge from nodes it's attached to */
-    private void _clearEdgeReferences(DiagramEdge e){
-    	for(int i=0; i<e.getNodesNum();i++){
-    		DiagramNode n = e.getNodeAt(i);
-    		EdgeReferenceMutableTreeNode reference;
-    		/* find the category tree node under which our reference is */ 
-    		
-    		reference = _lookForEdgeReference(n, e);
-    		assert(reference != null);
-    		
-    		treeModel.removeNodeFromParent(reference);
-    		DiagramModelTreeNode type = _lookForChild(n, e.getType());
-    		if(type.isLeaf())
-    			treeModel.removeNodeFromParent(type);   		
-    		n.removeEdge(e);
-    	}
-    }
-    
-    /* removes references from node */
-    private void _clearNodeReferences(DiagramNode n, Object source){
-    	/* remove the node itself from its external node, if any */
+	private void _removeInternalNodes(DiagramNode n, Object source){
+		for(int i=0; i<n.getInternalNodesNum(); i++){
+			DiagramNode innerNode = n.getInternalNodeAt(i);
+			_clearNodeReferences(innerNode, source);
+			_removeInternalNodes(innerNode, source);
+			n.removeInternalNode(innerNode);
+			nodes.remove(n);
+		}   	
+	}
+
+	/* removes both inner and tree node references to an edge from nodes it's attached to */
+	private void _clearEdgeReferences(DiagramEdge e){
+		for(int i=0; i<e.getNodesNum();i++){
+			DiagramNode n = e.getNodeAt(i);
+			EdgeReferenceMutableTreeNode reference;
+			/* find the category tree node under which our reference is */ 
+
+			reference = _lookForEdgeReference(n, e);
+			assert(reference != null);
+
+			treeModel.removeNodeFromParent(reference);
+			DiagramTreeNode type = _lookForChild(n, e.getType());
+			if(type.isLeaf())
+				treeModel.removeNodeFromParent(type);   		
+			n.removeEdge(e);
+		}
+	}
+
+	/* removes references from node */
+	private void _clearNodeReferences(DiagramNode n, Object source){
+		/* remove the node itself from its external node, if any */
 		if(n.getExternalNode() != null)
 			n.getExternalNode().removeInternalNode(n);
 		/* remove edges attached to this node from the collection */
@@ -580,202 +616,203 @@
 				edgesToRemove.add(e);
 			}else{
 				e.removeNode(n);
-				DiagramModelTreeNode nodeTreeReference = _lookForNodeReference(e, n);
+				DiagramTreeNode nodeTreeReference = _lookForNodeReference(e, n);
 				treeModel.removeNodeFromParent(nodeTreeReference);
 			}
-			
+
 		}
 		/* remove the edges that must no longer exist as were two ended edges attached to this node */
 		for(DiagramEdge e : edgesToRemove)
 			_takeOut(e, source);
-    }
-    
-    private void _change(ElementChangedEvent evt){
-    	String changeType = evt.getChangeType();
-    	/* don't use the event source as it might collide with other threads as 
-    	 * changes on the collections and inner changes on the node are synch'ed thought different monitors */
-    	/* treeModel.setEventSource(evt.getSource());*/
-    	if("name".equals(changeType)){
-    		if(evt.getSource() instanceof DiagramNode){
-    			DiagramNode n = (DiagramNode)evt.getSource();
-    			for(int i=0; i<n.getEdgesNum(); i++){
-    				DiagramEdge e = n.getEdgeAt(i);
-    				treeModel.nodeChanged(_lookForNodeReference(e,n));
-    				treeModel.nodeChanged(_lookForEdgeReference(n,e));
-    				for(int j=0; j<e.getNodesNum(); j++){
-    					DiagramNode n2 = e.getNodeAt(j);
-    					if(n2 != n)
-    						treeModel.nodeChanged(_lookForEdgeReference(n2,e));
-    				}
-    			}
-    			iLog("node name changed",DiagramElement.toLogString(n));
-    		}else{
-    			DiagramEdge e = (DiagramEdge)evt.getSource();
-    			for(int i=0; i<e.getNodesNum();i++){
-    				DiagramNode n = e.getNodeAt(i);
-    				treeModel.nodeChanged(_lookForEdgeReference(n,e));
-    			}
-    			iLog("edge name changed",DiagramElement.toLogString(e));
-    		}
-    		treeModel.nodeChanged(evt.getDiagramElement());
-    	}else if("properties".equals(changeType)){
-    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
-    		for(String type : n.getProperties().getTypes()){
-    			PropertyTypeMutableTreeNode typeNode = null;
-    			for(int i=0; i<n.getChildCount();i++){
-    				/* find the child treeNode corresponding to the current type */
-    				if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode)
-    					if(type.equals(((PropertyTypeMutableTreeNode)n.getChildAt(i)).getType())){
-    						typeNode = (PropertyTypeMutableTreeNode)n.getChildAt(i);
-    						break;
-    					}
-    			}
-    			
-    			if(typeNode == null) 
-    				throw new IllegalArgumentException("Inserted Node property type "+ type + " not present in the tree" );
-    			
-    			/* set the name and modifier string of all the children PropertyNodes */
-    			typeNode.setValues(n.getProperties().getValues(type), n.getProperties().getModifiers(type));
-    		}
-    		treeModel.nodeStructureChanged(evt.getDiagramElement());
-    		iLog("node properties changed",n.getProperties().toString());
-    	}else if("properties.clear".equals(changeType)){
-    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
-			List<String> empty = Collections.emptyList();
-			for(int i=0; i<n.getChildCount();i++){
-				/* find the child treeNode corresponding to the current type */
-				if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode){
-					((PropertyTypeMutableTreeNode)n.getChildAt(i)).setValues(empty, null);
+	}
+
+	/* the tree structure is changed as a consequence of this method, therefore it's synchronized *
+	 * so that external classes accessing the tree can get exclusive access through getMonitor()  */
+	private void _change(ElementChangedEvent evt){
+		synchronized(this){
+			String changeType = evt.getChangeType();
+			/* don't use the event source as it might collide with other threads as 
+			 * changes on the collections and inner changes on the node are synch'ed thought different monitors */
+			/* treeModel.setEventSource(evt.getSource());*/
+			if("name".equals(changeType)){
+				if(evt.getDiagramElement() instanceof DiagramNode){
+					DiagramNode n = (DiagramNode)evt.getDiagramElement();
+					for(int i=0; i<n.getEdgesNum(); i++){
+						DiagramEdge e = n.getEdgeAt(i);
+						treeModel.nodeChanged(_lookForNodeReference(e,n));
+						treeModel.nodeChanged(_lookForEdgeReference(n,e));
+						for(int j=0; j<e.getNodesNum(); j++){
+							DiagramNode n2 = e.getNodeAt(j);
+							if(n2 != n)
+								treeModel.nodeChanged(_lookForEdgeReference(n2,e));
+						}
+					}
+					iLog("node name changed",DiagramElement.toLogString(n));
+				}else{
+					DiagramEdge e = (DiagramEdge)evt.getDiagramElement();
+					for(int i=0; i<e.getNodesNum();i++){
+						DiagramNode n = e.getNodeAt(i);
+						treeModel.nodeChanged(_lookForEdgeReference(n,e));
+					}
+					iLog("edge name changed",DiagramElement.toLogString(e));
 				}
+				treeModel.nodeChanged(evt.getDiagramElement());
+			}else if("properties".equals(changeType)){
+				DiagramNode n = (DiagramNode)evt.getDiagramElement();
+				for(String type : n.getProperties().getTypes()){
+					PropertyTypeMutableTreeNode typeNode = null;
+					for(int i=0; i<n.getChildCount();i++){
+						/* find the child treeNode corresponding to the current type */
+						if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode)
+							if(type.equals(((PropertyTypeMutableTreeNode)n.getChildAt(i)).getType())){
+								typeNode = (PropertyTypeMutableTreeNode)n.getChildAt(i);
+								break;
+							}
+					}
+
+					if(typeNode == null) 
+						throw new IllegalArgumentException("Inserted Node property type "+ type + " not present in the tree" );
+
+					/* set the name and modifier string of all the children PropertyNodes */
+					typeNode.setValues(n.getProperties().getValues(type), n.getProperties().getModifiers(type));
+				}
+				treeModel.nodeStructureChanged(evt.getDiagramElement());
+				iLog("node properties changed",n.getProperties().toString());
+			}else if("properties.clear".equals(changeType)){
+				DiagramNode n = (DiagramNode)evt.getDiagramElement();
+				List<String> empty = Collections.emptyList();
+				for(int i=0; i<n.getChildCount();i++){
+					/* find the child treeNode corresponding to the current type */
+					if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode){
+						((PropertyTypeMutableTreeNode)n.getChildAt(i)).setValues(empty, null);
+					}
+				}
+				treeModel.nodeStructureChanged(evt.getDiagramElement());
+			}else if("property.add".equals(changeType)){
+				DiagramNode n = (DiagramNode)evt.getDiagramElement();
+				ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
+				PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
+				PropertyMutableTreeNode propertyNode = new PropertyMutableTreeNode(n.getProperties().getValues(args.getPropertyType()).get(args.getPropertyIndex()));
+				typeNode.add(propertyNode);
+				treeModel.insertNodeInto(propertyNode, typeNode, args.getPropertyIndex());
+				/* this is necessary to increment the child counter displayed between brackets */
+				treeModel.nodeChanged(typeNode);
+				iLog("property inserted",propertyNode.getName());
+			}else if("property.set".equals(changeType)){
+				DiagramNode n = (DiagramNode)evt.getDiagramElement();
+				ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
+				PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
+				((DiagramTreeNode)typeNode.getChildAt(args.getPropertyIndex()))
+				.setUserObject(n.getProperties().getValues(args.getPropertyType()).get(args.getPropertyIndex()));
+				treeModel.nodeChanged((typeNode.getChildAt(args.getPropertyIndex())));
+				iLog("property changed",n.getProperties().getValues(args.getPropertyType()).get(args.getPropertyIndex()));
+			}else if("property.remove".equals(changeType)){
+				DiagramNode n = (DiagramNode)evt.getDiagramElement();
+				ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
+				PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
+				iLog("property removed",((DiagramTreeNode)typeNode.getChildAt(args.getPropertyIndex())).getName()); //must do it before actual removing
+				treeModel.removeNodeFromParent((DiagramTreeNode)typeNode.getChildAt(args.getPropertyIndex()));
+				/* remove the bookmark keys associated with this property tree node, if any */
+				for(String key : treeModel.getBookmarks()){
+					treeModel.bookmarks.remove(key);
+				}
+			}else if("property.modifiers".equals(changeType)){
+				DiagramNode n = (DiagramNode)evt.getDiagramElement();
+				ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
+				PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
+				PropertyMutableTreeNode propertyNode = ((PropertyMutableTreeNode)typeNode.getChildAt(args.getPropertyIndex()));
+				StringBuilder builder = new StringBuilder();
+				Modifiers modifiers = n.getProperties().getModifiers(args.getPropertyType());
+				for(int index : modifiers.getIndexes(args.getPropertyIndex()))
+					builder.append(modifiers.getTypes().get(index)).append(' ');
+				propertyNode.setModifierString(builder.toString());
+				treeModel.nodeChanged(propertyNode);
+			}else if("arrowHead".equals(changeType)||"endLabel".equals(changeType)){
+				/* source is considered to be the node whose end of the edge was changed */
+				DiagramNode source = (DiagramNode)evt.getArguments();
+				DiagramEdge e = (DiagramEdge)evt.getDiagramElement();
+				treeModel.nodeChanged(e);
+				for(int i=0; i<e.getChildCount();i++){
+					NodeReferenceMutableTreeNode ref = (NodeReferenceMutableTreeNode)e.getChildAt(i);
+					if(ref.getNode() == source){
+						treeModel.nodeChanged(ref);
+						iLog(("arrowHead".equals(changeType) ? "arrow head changed" :"end label changed"),
+								"edge:"+DiagramElement.toLogString(e)+" node:"+DiagramElement.toLogString(ref.getNode())+
+								" value:"+ ("arrowHead".equals(changeType) ? e.getEndDescription(ref.getNode()): e.getEndLabel(ref.getNode())));
+						break;
+					}
+				}
+			}else if("notes".equals(changeType)){
+				/* do nothing as the tree update is taken care in the tree itself
+				 * and cannot do it here because it must work for all the diagram tree nodes */
+
 			}
-    		treeModel.nodeStructureChanged(evt.getDiagramElement());
-    	}else if("property.add".equals(changeType)){
-    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
-    		@SuppressWarnings("unchecked")
-			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
-    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
-    		PropertyMutableTreeNode propertyNode = new PropertyMutableTreeNode(n.getProperties().getValues(p.first).get(p.second));
-    		typeNode.add(propertyNode);
-    		treeModel.insertNodeInto(propertyNode, typeNode, p.second);
-    		/* this is necessary to increment the child counter displayed between brackets */
-    		treeModel.nodeChanged(typeNode);
-    		iLog("property inserted",propertyNode.getName());
-    	}else if("property.set".equals(changeType)){
-    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
-    		@SuppressWarnings("unchecked")
-			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
-    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
-    		((DiagramModelTreeNode)typeNode.getChildAt(p.second)).setUserObject(n.getProperties().getValues(p.first).get(p.second));
-    		treeModel.nodeChanged((typeNode.getChildAt(p.second)));
-    		iLog("property changed",n.getProperties().getValues(p.first).get(p.second));
-    	}else if("property.remove".equals(changeType)){
-    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
-    		@SuppressWarnings("unchecked")
-			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
-    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
-    		iLog("property removed",((DiagramModelTreeNode)typeNode.getChildAt(p.second)).getName()); //must do it before actual removing
-    		treeModel.removeNodeFromParent((DiagramModelTreeNode)typeNode.getChildAt(p.second));
-    		/* remove the bookmark keys associated with this property tree node, if any */
-    		for(String key : treeModel.getBookmarks()){
-    			treeModel.bookmarks.remove(key);
-    		}
-    	}else if("property.modifiers".equals(changeType)){
-    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
-    		@SuppressWarnings("unchecked")
-			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
-    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
-    		PropertyMutableTreeNode propertyNode = ((PropertyMutableTreeNode)typeNode.getChildAt(p.second));
-    		StringBuilder builder = new StringBuilder();
-			Modifiers modifiers = n.getProperties().getModifiers(p.first);
-			for(int index : modifiers.getIndexes(p.second))
-				builder.append(modifiers.getTypes().get(index)).append(' ');
-    		propertyNode.setModifierString(builder.toString());
-    		treeModel.nodeChanged(propertyNode);
-    	}else if("arrowHead".equals(changeType)||"endLabel".equals(changeType)){
-    		/* source is considered to be the node whose end of the edge was changed */
-    		DiagramNode source = (DiagramNode)evt.getSource();
-    		DiagramEdge e = (DiagramEdge)evt.getDiagramElement();
-    		treeModel.nodeChanged(e);
-    		for(int i=0; i<e.getChildCount();i++){
-    			NodeReferenceMutableTreeNode ref = (NodeReferenceMutableTreeNode)e.getChildAt(i);
-    			if(ref.getNode() == source){
-    				treeModel.nodeChanged(ref);
-    				iLog(("arrowHead".equals(changeType) ? "arrow head changed" :"end label changed"),
-    						"edge:"+DiagramElement.toLogString(e)+" node:"+DiagramElement.toLogString(ref.getNode())+
-    						" value:"+ ("arrowHead".equals(changeType) ? e.getEndDescription(ref.getNode()): e.getEndLabel(ref.getNode())));
-    				break;
-    			}
-    		}
-    	}else if("notes".equals(changeType)){
-    		/* do nothing as the tree update is taken care in the tree itself
-    		 * and cannot do it here because it must work for all the diagram tree nodes */
-    		
-    	}
-    	/* do nothing for other ElementChangedEvents as the position only concerns the diagram listeners */
-    	/* just forward the event to other listeners which might have been registered                     */ 
-    	diagramCollection.fireElementChanged(evt);
-    }
-    
-    private static DiagramModelTreeNode _lookForChild(DiagramModelTreeNode parentNode, String name){
-    	DiagramModelTreeNode child = null, temp;
-    	for(@SuppressWarnings("unchecked")
-		Enumeration<DiagramModelTreeNode> children = parentNode.children(); children.hasMoreElements();){
+		}
+		/* do nothing for other ElementChangedEvents as they only concern the diagram listeners */
+		/* just forward the event to other listeners which might have been registered           */ 
+		diagramCollection.fireElementChanged(evt);
+	}
+
+	private static DiagramTreeNode _lookForChild(DiagramTreeNode parentNode, String name){
+		DiagramTreeNode child = null, temp;
+		for(@SuppressWarnings("unchecked")
+				Enumeration<DiagramTreeNode> children = parentNode.children(); children.hasMoreElements();){
 			temp = children.nextElement();
 			if(temp.getName().equals(name)){
-				 child = temp;
-				 break;
+				child = temp;
+				break;
 			}
 		}
-    	return child;
-    }
-    
-    private static NodeReferenceMutableTreeNode _lookForNodeReference(DiagramEdge parent, DiagramNode n){
-    	NodeReferenceMutableTreeNode child = null, temp;
-    	for(@SuppressWarnings("unchecked")
-		Enumeration<DiagramModelTreeNode> children = parent.children(); children.hasMoreElements();){
-    		temp = (NodeReferenceMutableTreeNode)children.nextElement();
-    		if( ((NodeReferenceMutableTreeNode)temp).getNode().equals(n)){
-    			child = temp;
-    			break;
-    		}
-    	}
-    	return child;
-    }
-    
-    private static EdgeReferenceMutableTreeNode _lookForEdgeReference( DiagramNode parentNode, DiagramEdge e){
-    	DiagramModelTreeNode edgeType = _lookForChild(parentNode, e.getType());
-    	assert(edgeType != null);
-    	EdgeReferenceMutableTreeNode child = null, temp;
-    	for(@SuppressWarnings("unchecked")
-		Enumeration<DiagramModelTreeNode> children = edgeType.children(); children.hasMoreElements();){
-    		temp = (EdgeReferenceMutableTreeNode)children.nextElement();
-    		if( ((EdgeReferenceMutableTreeNode)temp).getEdge().equals(e)){
-    			child = temp;
-    			break;
-    		}
-    	}
-    	return child;
-    }
-    
-    private void iLog(String action,String args){
- 		   InteractionLog.log("MODEL",action,args);
-    }
-    
-	private DiagramModelTreeNode root;
-	InnerDiagramCollection diagramCollection;
+		return child;
+	}
+
+	private static NodeReferenceMutableTreeNode _lookForNodeReference(DiagramEdge parent, DiagramNode n){
+		NodeReferenceMutableTreeNode child = null, temp;
+		for(@SuppressWarnings("unchecked")
+				Enumeration<DiagramTreeNode> children = parent.children(); children.hasMoreElements();){
+			temp = (NodeReferenceMutableTreeNode)children.nextElement();
+			if( ((NodeReferenceMutableTreeNode)temp).getNode().equals(n)){
+				child = temp;
+				break;
+			}
+		}
+		return child;
+	}
+
+	private static EdgeReferenceMutableTreeNode _lookForEdgeReference( DiagramNode parentNode, DiagramEdge e){
+		DiagramTreeNode edgeType = _lookForChild(parentNode, e.getType());
+		assert(edgeType != null);
+		EdgeReferenceMutableTreeNode child = null, temp;
+		for(@SuppressWarnings("unchecked")
+				Enumeration<DiagramTreeNode> children = edgeType.children(); children.hasMoreElements();){
+			temp = (EdgeReferenceMutableTreeNode)children.nextElement();
+			if( ((EdgeReferenceMutableTreeNode)temp).getEdge().equals(e)){
+				child = temp;
+				break;
+			}
+		}
+		return child;
+	}
+
+	private void iLog(String action,String args){
+		InteractionLog.log("MODEL",action,args);
+	}
+
+	private DiagramTreeNode root;
+	private InnerDiagramCollection diagramCollection;
 	private ArrayList<N> nodes;
 	private ArrayList<E> edges;
 	private ArrayList<DiagramElement> elements;
 	private InnerTreeModel treeModel;
-	
+
 	private long edgeCounter;
 	private long nodeCounter;
-	
-	private ElementNotifier notifier;
+
+	private ReentrantLockNotifier notifier;
 	private List<ChangeListener> changeListeners;
-	
+
 	private boolean modified;
-	
+
 	private final static String ROOT_LABEL = "Diagram";
 	private final static int INITIAL_EDGES_SIZE = 20;
 	private final static int INITIAL_NODES_SIZE = 30;}
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModelTreeNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,289 +0,0 @@
-/*  
- CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-  
- Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-package uk.ac.qmul.eecs.ccmi.diagrammodel;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.swing.tree.DefaultMutableTreeNode;
-
-/**
- * This class represent a general node in a TreeModel
- *
- */
-@SuppressWarnings("serial")
-public abstract class DiagramModelTreeNode extends DefaultMutableTreeNode {
-	public DiagramModelTreeNode() {
-		super();
-		notes = "";
-		userObject = new UserObject();
-		setSuperClassUserObject(userObject);
-		bookmarkKeys = new ArrayList<String>();
-	}
-
-	public DiagramModelTreeNode(Object userObject) {
-		this();
-		setUserObject(userObject);
-	}
-	
-	/**
-	 * Each DiagramModelTreeNode keeps track of the bookmarks it has been assigned. Bookmarks
-	 * will affect how this tree node will be represented on a JTree: when a tree node is bookmarked 
-	 * an apex appears at the right of its name. 
-	 * @param key the bookmark 
-	 * @return true if this bookmark inner collection changed as a result of the call
-	 */
-	boolean addBookmarkKey(String key){
-		return bookmarkKeys.add(key);
-	}
-	
-	/**
-	 * Removes a bookmark key from the inner collection
-	 * @param key the key to remove 
-	 * @return true if this bookmark inner collection changed as a result of the call
-	 */
-	boolean removeBookmarkKey(String key){
-		return bookmarkKeys.remove(key);
-	}
-		
-	/**
-	 * Returns the the bookmarks currently associated to this tree node 
-	 * @return
-	 */
-	public List<String> getBookmarkKeys(){
-		return Collections.unmodifiableList(bookmarkKeys);
-	}
-	
-	public String getNotes(){
-		return notes;
-	}
-	
-	/**
-	 * Set a note for this tree node. A Note is a text the user wants to attach to a tree node. Notes
-	 * will affect how this tree node will be represented on a JTree: when a tree node is assigned a note 
-	 * a number sign (#) appears at the right of its name. 
-	 * @param note the text of the note 
-	 * @return true if this bookmark inner collection changed as a result of the call
-	 */
-	void setNotes(String note){
-		this.notes = note;
-	}
-	
-	/**
-	 * @see DefaultMutableTreeNode#getParent()
-	 */
-	@Override
-	public DiagramModelTreeNode getParent(){
-		return (DiagramModelTreeNode)super.getParent();
-	}
-	
-	/**
-	 * @see DefaultMutableTreeNode#getChildAt(int)
-	 */
-	@Override
-	public DiagramModelTreeNode getChildAt(int i){
-		return (DiagramModelTreeNode)super.getChildAt(i);
-	}
-	
-	/**
-	 * @see DefaultMutableTreeNode#getRoot()
-	 */
-	@Override
-	public DiagramModelTreeNode getRoot(){
-		return (DiagramModelTreeNode)super.getRoot();
-	}
-	
-	/**
-	 * @see DefaultMutableTreeNode#setUserObject(Object)
-	 */
-	@Override
-	public void	setUserObject(Object userObject){
-		((UserObject)this.userObject).setObject(userObject);
-	}
-	
-	/**
-	 * @see DefaultMutableTreeNode#getUserObject()
-	 */
-	@Override
-	public Object getUserObject(){
-		return userObject;
-	}
-	
-	/**
-	 * Return a String representing this object for this tree node in a way more suitable 
-	 * for a text to speech synthesizer to read, than toString(). 
-	 * @return a String suitable for text to speech synthesis
-	 */
-	public String spokenText(){
-		return ((UserObject)userObject).spokenText();
-	}
-	
-	/**
-	 * Returns a more detailed description of the tree node than {@link #spokenText()}.
-	 * 
-	 *  @return a description of the tree node
-	 */
-	public String detailedSpokenText(){
-		return spokenText();
-	}
-	
-	/**
-	 * returns the tree node name "as it is", without any decoration such as notes, bookmarks or cardinality.
-	 * Unlike the String returned by toString
-	 * @return the tree node name
-	 */
-	public String getName(){
-		return ((UserObject)userObject).getName();
-	}
-	
-	/**
-	 * @see DefaultMutableTreeNode#isRoot()
-	 */
-	@Override
-	public boolean isRoot(){
-		return false; // root node overwrites this method
-	}
-
-	/**
-	 * @see DefaultMutableTreeNode#getLastLeaf()
-	 */
-	@Override
-	public DiagramModelTreeNode getLastLeaf() {
-		return (DiagramModelTreeNode)super.getLastLeaf();
-	}
-
-	/**
-	 * @see DefaultMutableTreeNode#getNextLeaf()
-	 */
-	@Override
-	public DiagramModelTreeNode getNextLeaf() {
-		return (DiagramModelTreeNode)super.getNextLeaf();
-	}
-
-	/**
-	 * @see DefaultMutableTreeNode#getNextNode()
-	 */
-	@Override
-	public DiagramModelTreeNode getNextNode() {
-		return (DiagramModelTreeNode)super.getNextNode();
-	}
-
-	/**
-	 * @see DefaultMutableTreeNode#getNextSibling()
-	 */
-	@Override
-	public DiagramModelTreeNode getNextSibling() {
-		return (DiagramModelTreeNode)super.getNextSibling();
-	}
-
-	/**
-	 * @see DefaultMutableTreeNode#getPreviousLeaf()
-	 */
-	@Override
-	public DiagramModelTreeNode getPreviousLeaf() {
-		return (DiagramModelTreeNode)super.getPreviousLeaf();
-	}
-
-	/**
-	 * @see DefaultMutableTreeNode#getPreviousNode()
-	 */
-	@Override
-	public DiagramModelTreeNode getPreviousNode() {
-		return (DiagramModelTreeNode)super.getPreviousNode();
-	}
-
-	/**
-	 * @see DefaultMutableTreeNode#getPreviousSibling()
-	 */
-	@Override
-	public DiagramModelTreeNode getPreviousSibling() {
-		return (DiagramModelTreeNode)super.getPreviousSibling();
-	}
-	
-	private void setSuperClassUserObject(Object u){
-		super.setUserObject(u);
-	}
-	
-	private UserObject getUserObjectInstance(){
-		return new UserObject();
-	}
-	
-	protected List<String> bookmarkKeys;
-	protected String notes;
-	/* hides the DefaultMutableTreeNode protected field */
-	private Object userObject;
-	protected static final char NOTES_CHAR = '#';
-	protected static final char BOOKMARK_CHAR = '\'';
-	protected static final String BOOKMARK_SPEAK = ", bookmarked";
-	protected static final String NOTES_SPEAK = ", has notes";
-	
-	@Override
-	public Object clone(){
-		DiagramModelTreeNode clone = (DiagramModelTreeNode )super.clone();
-		clone.notes = "";
-		clone.bookmarkKeys = new ArrayList<String>();
-		clone.userObject = clone.getUserObjectInstance();	
-		clone.setSuperClassUserObject(clone.userObject);
-		return clone;
-	}
-	
-	/* this works as a wrapper for the real user object in order to provide */
-	/* decoration on the treeNode label to signal and/or bookmarks          */
-	private class UserObject {
-		private Object object;
-
-		public UserObject(){
-			object = "";
-		}
-		public void setObject(Object o){
-			this.object = o;
-		}
-
-		@Override
-		public boolean equals(Object o){
-			return this.object.equals(o);
-		}
-		
-		@Override
-		public String toString(){
-			StringBuilder builder = new StringBuilder(object.toString());
-			if(!"".equals(notes)){
-				builder.append(NOTES_CHAR);
-			}
-			if(!bookmarkKeys.isEmpty())
-				builder.append(BOOKMARK_CHAR);
-			return builder.toString();
-		}
-		
-		public String spokenText(){
-			StringBuilder builder = new StringBuilder(object.toString());
-			if(!"".equals(notes)){
-				builder.append(NOTES_SPEAK);
-			}
-			if(!bookmarkKeys.isEmpty())
-				builder.append(BOOKMARK_SPEAK);
-			return builder.toString();
-		}
-		
-		public String getName(){
-			return object.toString();
-		}
-	}
-}
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -18,10 +18,11 @@
 */
 package uk.ac.qmul.eecs.ccmi.diagrammodel;
 
+import java.util.List;
 import java.util.Set;
 
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent.PropertyChangeArgs;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
-import uk.ac.qmul.eecs.ccmi.utils.Pair;
 
 /**
  * This class represents a node in the diagram.  
@@ -35,15 +36,6 @@
 	}
 
 	/**
-	 * @see DiagramModelTreeNode#setNotes(String)
-	 */
-	@Override
-	void setNotes(String notes){
-		this.notes = notes;
-		notifyChange(new ElementChangedEvent(this,this,"note"));
-	}
-
-	/**
 	 * Returns the properties of this node. Be aware that what is returned is the reference
 	 * to the actual NodeProperties object inside this DiagramNode. Thereforemodifying the returned
 	 * object will affect this node.
@@ -77,9 +69,9 @@
 	 * Set the NodeProperties of this node
 	 * @param properties  the properties to set for this node
 	 */
-	public  void setProperties(NodeProperties properties){
+	public void setProperties(NodeProperties properties, Object source){
 		this.properties = properties;
-		notifyChange(new ElementChangedEvent(this.properties,this,"properties"));
+		notifyChange(new ElementChangedEvent(this,this.properties,"properties",source));
 	}
 
 	/**
@@ -87,46 +79,54 @@
 	 * @see NodeProperties#addValue(String, String)
 	 * 
 	 */
-	public void addProperty(String propertyType, String propertyValue){
+	public void addProperty(String propertyType, String propertyValue, Object source){
 		getProperties().addValue(propertyType, propertyValue);
 		int index = getProperties().getValues(propertyType).size() - 1;
-		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,index),this,"property.add"));
+		notifyChange(new ElementChangedEvent(this,new PropertyChangeArgs(propertyType,index,""),"property.add",source));
 	}
 
 	/**
 	 * Removes a property from the NodeProperties of this node
 	 * @see NodeProperties#removeValue(String, int)
 	 */
-	public void removeProperty(String propertyType, int valueIndex){
+	public void removeProperty(String propertyType, int valueIndex, Object source){
+		String oldValue = getProperties().getValues(propertyType).get(valueIndex);
 		getProperties().removeValue(propertyType, valueIndex);
-		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,valueIndex),this,"property.remove"));
+		notifyChange(new ElementChangedEvent(this,new PropertyChangeArgs(propertyType,valueIndex,oldValue),"property.remove",source));
 	}
 
 	/**
 	 * Set a property on the NodeProperties of this node to a new value
 	 * @see NodeProperties#setValue(String, int, String) 
 	 */
-	public void setProperty(String propertyType, int valueIndex, String newValue){
+	public void setProperty(String propertyType, int valueIndex, String newValue, Object source){
+		String oldValue = getProperties().getValues(propertyType).get(valueIndex);
 		getProperties().setValue(propertyType, valueIndex, newValue);
-		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,valueIndex),this,"property.set"));
+		notifyChange(new ElementChangedEvent(this,new PropertyChangeArgs(propertyType,valueIndex,oldValue),"property.set",source));
 	}
 
 	/**
 	 * Removes all the values in the NodeProperties of this node
 	 * @see NodeProperties#clear()
 	 */
-	public void clearProperties(){
+	public void clearProperties(Object source){
 		getProperties().clear();
-		notifyChange(new ElementChangedEvent(this,this,"properties.clear"));
+		notifyChange(new ElementChangedEvent(this,this,"properties.clear",source));
 	}
 
 	/**
 	 * set the modifier indexes in the NodeProperties of this node
 	 * @see Modifiers#setIndexes(int, Set)
 	 */
-	public void setModifierIndexes(String propertyType, int propertyValueIndex, Set<Integer> modifierIndexes){
+	public void setModifierIndexes(String propertyType, int propertyValueIndex, Set<Integer> modifierIndexes,Object source){
+		StringBuilder oldIndexes = new StringBuilder();
+		List<String> modifierTypes = getProperties().getModifiers(propertyType).getTypes();
+		Set<Integer> indexes = getProperties().getModifiers(propertyType).getIndexes(propertyValueIndex);
+		for(Integer I : indexes){
+			oldIndexes.append(modifierTypes.get(I)).append(' ');
+		}
 		getProperties().getModifiers(propertyType).setIndexes(propertyValueIndex, modifierIndexes);
-		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,propertyValueIndex),this,"property.modifiers"));
+		notifyChange(new ElementChangedEvent(this,new PropertyChangeArgs(propertyType,propertyValueIndex,oldIndexes.toString()),"property.modifiers",source));
 	}
 
 	/**
@@ -143,7 +143,7 @@
 		builder.append(getName());
 		builder.append('.').append(' ');
 		for(int i=0; i<getChildCount();i++){
-			DiagramModelTreeNode treeNode = (DiagramModelTreeNode) getChildAt(i);
+			DiagramTreeNode treeNode = (DiagramTreeNode) getChildAt(i);
 			if(treeNode.getChildCount() > 0){
 				builder.append(treeNode.getChildCount())
 				.append(' ')
@@ -170,13 +170,13 @@
 
 	/**
 	 * add an edge to the attached edges list
-	 * @param the edge to be added
+	 * @param e the edge to be added
 	 */
 	public abstract boolean addEdge(DiagramEdge e);
 
 	/**
 	 * Removes an edge from the attached edges list
-	 * @param the edge to be removed 
+	 * @param e the edge to be removed 
 	 */
 	public abstract boolean removeEdge(DiagramEdge e);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,292 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.diagrammodel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * This class represent a general node in a TreeModel
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class DiagramTreeNode extends DefaultMutableTreeNode {
+	/**
+	 * Creates a tree node with the default user object. The default user object has no label. Therefore 
+	 * this node will have no label when displayed on a tree.
+	 */
+	public DiagramTreeNode() {
+		super();
+		notes = "";
+		userObject = new UserObject();
+		setSuperClassUserObject(userObject);
+		bookmarkKeys = new ArrayList<String>();
+	}
+
+	/**
+	 * Creates a tree node, holding the user object passed as argument. The label of the 
+	 * tree node will be the string returned by {@code userObject.toString()}
+	 * 
+	 * @param userObject the user object for this tree node
+	 * 
+	 * @see javax.swing.tree.DefaultMutableTreeNode
+	 */
+	public DiagramTreeNode(Object userObject) {
+		this();
+		setUserObject(userObject);
+	}
+	
+	/**
+	 * Each DiagramModelTreeNode keeps track of the bookmarks it has been assigned. Bookmarks
+	 * will affect how this tree node will be represented on a JTree: when a tree node is bookmarked 
+	 * an apex appears at the right of its name. 
+	 * 
+	 * @param key the bookmark 
+	 * @return true if this bookmark inner collection changed as a result of the call
+	 */
+	boolean addBookmarkKey(String key){
+		return bookmarkKeys.add(key);
+	}
+	
+	/**
+	 * Removes a bookmark key from the inner collection.
+	 * 
+	 * @param key the key to remove 
+	 * @return true if this bookmark inner collection changed as a result of the call
+	 */
+	boolean removeBookmarkKey(String key){
+		return bookmarkKeys.remove(key);
+	}
+		
+	/**
+	 * Returns the the bookmark keys currently associated to this tree node in the tree model.
+	 * 
+	 * @return a n unmodifiable list of strings used as keys for bookmarks
+	 */
+	public List<String> getBookmarkKeys(){
+		return Collections.unmodifiableList(bookmarkKeys);
+	}
+	
+	public String getNotes(){
+		return notes;
+	}
+	
+	/**
+	 * Set a note for this tree node. A Note is a text the user wants to attach to a tree node. Notes
+	 * will affect how this tree node will be represented on a JTree: when a tree node is assigned a note 
+	 * a number sign (#) appears at the right of its name. 
+	 * 
+	 * @param note the text of the note
+	 * @param source used by {@code DiagramElement} to trigger {@code ElementChangeEvents}     
+	 * 
+	 * @see DiagramElement#setNotes(String, Object)
+	 */
+	protected void setNotes(String note, Object source){
+		this.notes = note;
+	}
+	
+	@Override
+	public DiagramTreeNode getParent(){
+		return (DiagramTreeNode)super.getParent();
+	}
+	
+	@Override
+	public DiagramTreeNode getChildAt(int i){
+		return (DiagramTreeNode)super.getChildAt(i);
+	}
+	
+	@Override
+	public DiagramTreeNode getRoot(){
+		return (DiagramTreeNode)super.getRoot();
+	}
+	
+	@Override
+	public void	setUserObject(Object userObject){
+		((UserObject)this.userObject).setObject(userObject);
+	}
+	
+	@Override
+	public Object getUserObject(){
+		return userObject;
+	}
+	
+	/**
+	 * Return a String representing this object for this tree node in a way more suitable 
+	 * for a text to speech synthesizer to read, than toString(). 
+	 * 
+	 * @return a String suitable for text to speech synthesis
+	 */
+	public String spokenText(){
+		return ((UserObject)userObject).spokenText();
+	}
+	
+	/**
+	 * Returns a more detailed description of the tree node than {@link #spokenText()}.
+	 * 
+	 *  @return a description of the tree node
+	 */
+	public String detailedSpokenText(){
+		return spokenText();
+	}
+	
+	/**
+	 * returns the tree node name "as it is", without any decoration such as notes, bookmarks or cardinality;
+	 * unlike the String returned by toString.
+	 * 
+	 * @return the tree node name
+	 */
+	public String getName(){
+		return ((UserObject)userObject).getName();
+	}
+	
+	@Override
+	public boolean isRoot(){
+		return false; // root node overwrites this method
+	}
+
+	@Override
+	public DiagramTreeNode getLastLeaf() {
+		return (DiagramTreeNode)super.getLastLeaf();
+	}
+
+	@Override
+	public DiagramTreeNode getNextLeaf() {
+		return (DiagramTreeNode)super.getNextLeaf();
+	}
+
+	@Override
+	public DiagramTreeNode getNextNode() {
+		return (DiagramTreeNode)super.getNextNode();
+	}
+
+	@Override
+	public DiagramTreeNode getNextSibling() {
+		return (DiagramTreeNode)super.getNextSibling();
+	}
+
+	@Override
+	public DiagramTreeNode getPreviousLeaf() {
+		return (DiagramTreeNode)super.getPreviousLeaf();
+	}
+
+	@Override
+	public DiagramTreeNode getPreviousNode() {
+		return (DiagramTreeNode)super.getPreviousNode();
+	}
+
+	@Override
+	public DiagramTreeNode getPreviousSibling() {
+		return (DiagramTreeNode)super.getPreviousSibling();
+	}
+	
+	private void setSuperClassUserObject(Object u){
+		super.setUserObject(u);
+	}
+	
+	private UserObject getUserObjectInstance(){
+		return new UserObject();
+	}
+	
+	/**
+	 * The bookmarks, involving this node, entered by the user in the DiagramTree this node belongs to. 
+	 */
+	protected List<String> bookmarkKeys;
+	/**
+	 * The notes set by the user for this node.
+	 */
+	protected String notes;
+	/* hides the DefaultMutableTreeNode protected field */
+	private Object userObject;
+	/**
+	 * The character that is appended to the label of this node when the user enters some notes for it. 
+	 */
+	protected static final char NOTES_CHAR = '#';
+	/**
+	 * The character that is appended to the label of this node when it's bookmarked by the user.
+	 */
+	protected static final char BOOKMARK_CHAR = '\'';
+	/**
+	 * The string that is appended to the spoken text of this node when the user enters some notes for it.
+	 * 
+	 * @see #spokenText()
+	 */
+	protected static final String BOOKMARK_SPEAK = ", bookmarked";
+	/**
+	 * The string that is appended to the spoken text of this node when it's bookmarked by the user.
+	 * 
+	 * @see #spokenText()
+	 */
+	protected static final String NOTES_SPEAK = ", has notes";
+	
+	@Override
+	public Object clone(){
+		DiagramTreeNode clone = (DiagramTreeNode )super.clone();
+		clone.notes = "";
+		clone.bookmarkKeys = new ArrayList<String>();
+		clone.userObject = clone.getUserObjectInstance();	
+		clone.setSuperClassUserObject(clone.userObject);
+		return clone;
+	}
+	
+	/* this works as a wrapper for the real user object in order to provide */
+	/* decoration on the treeNode label to signal and/or bookmarks          */
+	private class UserObject {
+		private Object object;
+
+		public UserObject(){
+			object = "";
+		}
+		public void setObject(Object o){
+			this.object = o;
+		}
+
+		@Override
+		public boolean equals(Object o){
+			return this.object.equals(o);
+		}
+		
+		@Override
+		public String toString(){
+			StringBuilder builder = new StringBuilder(object.toString());
+			if(!"".equals(notes)){
+				builder.append(NOTES_CHAR);
+			}
+			if(!bookmarkKeys.isEmpty())
+				builder.append(BOOKMARK_CHAR);
+			return builder.toString();
+		}
+		
+		public String spokenText(){
+			StringBuilder builder = new StringBuilder(object.toString());
+			if(!"".equals(notes)){
+				builder.append(NOTES_SPEAK);
+			}
+			if(!bookmarkKeys.isEmpty())
+				builder.append(BOOKMARK_SPEAK);
+			return builder.toString();
+		}
+		
+		public String getName(){
+			return object.toString();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramTreeNodeEvent.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,75 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package uk.ac.qmul.eecs.ccmi.diagrammodel;
+
+import java.util.EventObject;
+
+/**
+ * An event that is triggered in the {@code DiagramModel} when an action 
+ * happens that can involve all the tree nodes of the model's {@code DiagramTree} 
+ * rather than only nodes or edges. 
+ * 
+ * Particularly such actions happens when the user set the notes for a tree node or when 
+ * they bookmark a tree node. 
+ *
+ */
+@SuppressWarnings("serial")
+public class DiagramTreeNodeEvent extends EventObject {
+	/**
+	 * Creates a new event related to a change in a tree node on either notes or bookmarks.   
+	 * In order to get the status of the tree node before the change the old value is passed as
+	 * parameter to this constructor.  
+	 * For {@code setNotes} this value is the notes before the change. For {@code removeBookmark} it
+	 * will be the bookmark that has just been removed. Finally for {@code putBookmark()} the value is the
+	 * new bookmark.    
+	 * 
+	 * @see uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel
+	 * 
+	 * @param treeNode the tree node where the action happened 
+	 * @param value the value that the tree node had before this action. 
+	 * @param source the source of the action 
+	 */
+	public DiagramTreeNodeEvent(DiagramTreeNode treeNode, String value, Object source){
+		super(source);
+		this.treeNode = treeNode;
+		this.value = value;
+	}
+
+	/**
+	 * Returns the tree node on which a new note has been set or a bookmark has been added/removed.
+	 *    
+	 * @return the tree node this event refers to.  
+	 */
+	public DiagramTreeNode getTreeNode() {
+		return treeNode;
+	}
+	
+	/**
+	 * Returns the old value before the action (the old notes for {@code setNotes()}, 
+	 * and the bookmark key for {@code putBookmark() and removeBookmark()}.
+	 * 
+	 * @return the old value before the action
+	 */
+	public String getValue() {
+		return value;
+	}
+
+	private DiagramTreeNode treeNode;
+	private String value;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramTreeNodeListener.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,49 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package uk.ac.qmul.eecs.ccmi.diagrammodel;
+
+/**
+ * The listener interface for receiving {@code DiagramTreeNode} events. This events 
+ * happen when the user changes the state of any tree node in the {@code DiagramTree}, 
+ * not necessarily (but possibly) a {@code DiagramNode} or {@code DiagramEdge}.
+ *
+ */
+public interface DiagramTreeNodeListener {
+	
+	/**
+	 * Called when a new bookmark is added to {@code DiagramTree}.
+	 * 
+	 * @param evt the event object representing a new bookmark insertion in the {@code DiagramTree}.
+	 */
+	public void bookmarkAdded(DiagramTreeNodeEvent evt);
+	
+	/**
+	 * Called when a bookmark is removed from the {@code DiagramTree}.
+	 * 
+	 * @param evt the event object representing a new bookmark insertion in the {@code DiagramTree}.
+	 */
+	public void bookmarkRemoved(DiagramTreeNodeEvent evt);
+
+	/**
+	 * Called when a note is set on a node in the  {@code DiagramTree}.
+	 * 
+	 * @param evt evt the event object representing a note set on a node in the {@code DiagramTree}.
+	 */
+	public void notesChanged(DiagramTreeNodeEvent evt);
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceHolderMutableTreeNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceHolderMutableTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -20,14 +20,12 @@
 
 /**
  * This class is a special tree node which holds the EdgeReferenceMutableTreeNode
- * in the tree layout, where It is normally placed as a Node's child.
- * 
- *
+ * in the tree layout, where It is normally placed as a {@code DiagramNode}'s child.
  */
 @SuppressWarnings("serial")
-public class EdgeReferenceHolderMutableTreeNode extends DiagramModelTreeNode {
+public class EdgeReferenceHolderMutableTreeNode extends DiagramTreeNode {
 
-	public EdgeReferenceHolderMutableTreeNode(Object userObj){
+	EdgeReferenceHolderMutableTreeNode(Object userObj){
 		super(userObj);
 	}
 	
@@ -39,8 +37,9 @@
 	}
 	
 	/**
-	 * Return a String representing this object for this tree node in a way more suitable 
-	 * for a text to speech synthesizer to read, than toString(). 
+	 * Returns a String representing this object for this tree node in a way more suitable 
+	 * for a text to speech synthesizer to read, than {@code toString()}. 
+	 * 
 	 * @return a String suitable for text to speech synthesis
 	 */
 	@Override
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceMutableTreeNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceMutableTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,14 +19,12 @@
 package uk.ac.qmul.eecs.ccmi.diagrammodel;
 
 /**
- * 
  * The diagramModeltreeNode placed in a node subtree representing an edge connecting 
  * that node with another node.
- *
  */
 @SuppressWarnings("serial")
-public class EdgeReferenceMutableTreeNode extends DiagramModelTreeNode {
-	public EdgeReferenceMutableTreeNode(DiagramEdge edge, DiagramNode node){
+public class EdgeReferenceMutableTreeNode extends DiagramTreeNode {
+	EdgeReferenceMutableTreeNode(DiagramEdge edge, DiagramNode node){
 		super();
 		this.edge = edge;
 		this.node = node;
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ElementChangedEvent.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ElementChangedEvent.java	Wed Apr 25 17:09:09 2012 +0100
@@ -21,17 +21,28 @@
 import java.util.EventObject;
 
 /**
- *  ElementChangedEvent is used to notify the model listeners that an element 
- *  in the model has been changed (e.g. it has a new name).
+ *  ElementChangedEvent is used to notify the model listeners that an element ({@code DiagramNode}
+ *  or {@code DiagramEdge}) in the model has been changed (e.g. it has a new name).
  *
  */
 @SuppressWarnings("serial")
 public class ElementChangedEvent extends EventObject {
 	
-	public ElementChangedEvent( Object source, DiagramElement element, String changeType) {
+	/**
+	 * Creates a new instance of {@code ElementChangedEvent}
+	 * 
+	 * @param element the element that has been changed 
+	 * @param args 
+	 * @param changeType it's a {@code String} identifying the change type. Subclasses of {@code DiagramNode} and
+	 * {@code DiagramEdge} can define their own change events and and make listeners aware of them via their
+	 * {@code notifyChnage()} method. Listeners (defined outside this package as well) can then identify such changes using this string. 
+	 * @param source the source of the change that triggered this event 
+	 */
+	public ElementChangedEvent( DiagramElement element, Object args, String changeType, Object source) {
 		super(source);
 		this.changeType = changeType;
 		this.element = element;
+		this.arguments = args;
 	}
 
 	/**
@@ -53,6 +64,46 @@
 		return element;
 	}
 	
+	/**
+	 * Returns the arguments of the change if the the change type has any 
+	 * 
+	 * @return an object representing the arguments or null
+	 */
+	public Object getArguments(){
+		return arguments;
+	}
+	
 	private String changeType;
-	DiagramElement element;
+	private DiagramElement element;
+	private Object arguments;
+	
+	/**
+	 * This class is returned by {@link ElementChangedEvent#getArguments()} when a node property is 
+	 * changed. It holds the informations about the property type and the property index, so that 
+	 * listeners can retrieve the property when handling the event. 
+	 *
+	 */
+	public static class PropertyChangeArgs {
+		public PropertyChangeArgs(String type, int index, String oldValue){
+			this.type = type;
+			this.index = index;
+			this.oldValue = oldValue;
+		}
+		
+		public String getPropertyType(){
+			return type;
+		}
+		
+		public int getPropertyIndex(){
+			return index;
+		}
+		
+		public String getOldValue(){
+			return oldValue;
+		}
+		
+		private String type;
+		private String oldValue;
+		private int index;
+	}
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/MalformedEdgeException.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/MalformedEdgeException.java	Wed Apr 25 17:09:09 2012 +0100
@@ -1,3 +1,21 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
 /*  
  CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
   
@@ -26,11 +44,13 @@
 @SuppressWarnings("serial")
 public class MalformedEdgeException extends RuntimeException {
 
-	public MalformedEdgeException(DiagramEdge edge, String message) {
+	/**
+	 * Creates a new {@code MalformedEdgeException} holding the messsage passed as argument.
+	 * The message can be accessed by calling {@code getMessage()} on this exception. 
+	 * 
+	 * @param message
+	 */
+	public MalformedEdgeException(String message) {
 		super("Edge inserted into data structure was malformed for this reason:" + message);
 	}
-
-	public MalformedEdgeException(Throwable arg0) {
-		super(arg0);
-	}
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeProperties.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeProperties.java	Wed Apr 25 17:09:09 2012 +0100
@@ -88,17 +88,17 @@
 	
 	/**
 	 * Returns the types of properties.
-	 * @return
+	 * 
+	 * @return a list of strings holding types of properties 
 	 */
 	public List<String> getTypes(){
 		return types;
 	}
 	
 	/**
-	 * 
-	 * @param types the property type we want to get the values of.
+	 * @param propertyType the property type we want to get the values of.
 	 * @return an array of string with the different properties set by the user or
-	 * null if the specified type does not exist.
+	 * {@code null} if the specified type does not exist.
 	 */
 	public List<String> getValues(String propertyType){
 		Entry e = properties.get(propertyType);
@@ -110,7 +110,8 @@
 	/**
 	 * Returns the view object associated with a property type in this NodeProperties instance. A view object is 
 	 * defined by the client of this class and it holds the information needed for a visual representation
-	 * of the property. 
+	 * of the property.
+	 * 
 	 * @param type the property type the returned view is associated with
 	 * @return the View object or null if non has been set previously 
 	 */
@@ -124,7 +125,8 @@
 	/**
 	 * Sets the View object for the a type of properties. The NodeProperties only works 
 	 * as a holder as for the view objects. The client code of this class has to define
-	 * its own view, which will then be used by    
+	 * its own view.    
+	 * 
 	 * @param type the type of property the view is associated with. 
 	 * @param o an object defined by user
 	 * @see #getView(String)
@@ -137,7 +139,6 @@
 	}
 	
 	/**
-	 * 
 	 * Returns a reference to the modifier object associated with a property type. Changes to the returned 
 	 * reference will affect the internal state of the NodeProperty object.
 	 * 
@@ -154,7 +155,8 @@
 	}
 	
 	/**
-	 * Adds a value of the type passed as argument 
+	 * Adds a value of the type passed as argument.
+	 * 
 	 * @param propertyType a property type defined in the property type definition passed as argument to the constructor
 	 * @param propertyValue the value to add to the property type
 	 */
@@ -169,7 +171,8 @@
 	
 	/**
 	 * Adds a value of the type passed as argument and sets the modifier indexes for it as for
-	 *  {@link Modifiers#setIndexes(int, Set)} 
+	 *  {@link Modifiers#setIndexes(int, Set)}.
+	 *  
 	 * @param propertyType a property type  
 	 * @param propertyValue a property value 
 	 * @param modifierIndexes the modifier set of indexes
@@ -192,7 +195,8 @@
 	}
 	
 	/**
-	 * Removes the value at the specified index for the specified property type
+	 * Removes the value at the specified index for the specified property type.
+	 * 
 	 * @param propertyType a property type  
 	 * @param valueIndex the index of the value to remove 
 	 * @return the removed value 
@@ -208,11 +212,12 @@
 	}
 	
 	/**
-	 * Sets the value of a property type at the specified index to a new value  
+	 * Sets the value of a property type at the specified index to a new value.
+	 *   
 	 * @param propertyType a property type 
 	 * @param valueIndex the index of the value which must be replaced
 	 * @param newValue the new value for the specified index 
-	 * @throws IllegalArgumentException if propertyType 
+	 * @throws IllegalArgumentException if propertyType
 	 * is not among the ones in the type definition passed as argument to the constructor
 	 */
 	public void setValue(String propertyType, int valueIndex, String newValue){
@@ -223,7 +228,8 @@
 	}
 	
 	/**
-	 * Removes all the values and modifiers for a specific type 
+	 * Removes all the values and modifiers for a specific type.
+	 * 
 	 * @param propertyType the type whose property and modifiers must be removed
 	 * @throws IllegalArgumentException if propertyType 
 	 * is not among the ones in the type definition passed as argument to the constructor
@@ -246,7 +252,8 @@
 	}
 	
 	/**
-	 * Returns true if this NodeProperties contains no values
+	 * Returns true if this NodeProperties contains no values.
+	 * 
 	 * @return true if this NodeProperties contains no values
 	 */
 	public boolean isEmpty(){
@@ -260,7 +267,8 @@
 	}
 	
 	/**
-	 * Returns true if there are no values for the specified type in this NodeProperties instance
+	 * Returns true if there are no values for the specified type in this NodeProperties instance.
+	 * 
 	 * @param propertyType
 	 * @return true if there are no values for the specified type in this NodeProperties instance
 	 */
@@ -273,7 +281,8 @@
 	
 	/**
 	 * true if this NodeProperties object has no types. This can happen if the constructor 
-	 * has been called with an empty or null property type definition
+	 * has been called with an empty or null property type definition.
+	 * 
 	 * @return true if this NodeProperties object has no types
 	 */
 	public boolean isNull(){
@@ -282,7 +291,7 @@
 	
 	/**
 	 * Returns a string representation of types, value and modifiers of this property. Such a 
-	 * representation can be passed as argument to {@link #valueOf(String)} to populate a NodeProperties
+	 * representation can be passed as argument to {@link #fill(String)} to populate a NodeProperties
 	 * object. Such NodeProperties object though must have the same type and modifier definition of
 	 * the object this method is called on.
 	 * 
@@ -309,7 +318,7 @@
 
 	/**
 	 * Fills up the this property according to the string passed as arguments 
-	 * The string must be generated by calling toString on a NodeProeprty instance
+	 * The string must be generated by calling toString on a NodeProperty instance
 	 * holding the same property types as type, otherwise an IllegalArgumentException 
 	 * is likely to be thrown.   
 	 * 
@@ -318,7 +327,7 @@
 	 * @throws IllegalArgumentException if s contains property types which 
 	 * are not among the ones in the type definition passed as argument to the constructor
 	 */
-	public void valueOf(String s){
+	public void fill(String s){
 		/* clear up previous values */
 		clear();
 		
@@ -411,6 +420,7 @@
 		/**
 		 * Returns the list of modifier types, as per the type definition passed as argument to the NodeProperties
 		 * constructor.
+		 * 
 		 * @return a list of modifier types
 		 * @see NodeProperties#NodeProperties(LinkedHashMap)
 		 */
@@ -422,7 +432,8 @@
 		 * Returns the view object associated with a modifier type in this Modifier instance. A view object is 
 		 * defined by the client of this class and it holds the information needed for a visual representation
 		 * of the modifier. 
-		 * @param type the property type the returned view is associated with
+		 * 
+		 * @param modifierType the property type the returned view is associated with
 		 * @return the View object or null if non has been set previously 
 		 */
 		public Object getView(String modifierType){
@@ -433,7 +444,8 @@
 		 * Sets the View object for the a type of modifier. The NodeProperties only works 
 		 * as a holder as for the view objects. The client code of this class has to define
 		 * its own view, which will then be used by the client itself to visualise the modifier    
-		 * @param type the type of modifier the view is associated with. 
+		 * 
+		 * @param modifierType the type of modifier the view is associated with. 
 		 * @param o an object defined by user
 		 * @see #getView(String)
 		 * @throws IllegalArgumentException if modifierType 
@@ -462,6 +474,7 @@
 		
 		/**
 		 * Set the modifier indexes for the property value at the index passed as argument 
+		 * 
 		 * @param propertyValueIndex the index of the property value 
 		 * @param modifierIndexes a set of indexes which refer to the list returned by {@link #getTypes()}
 		 * @throws ArrayIndexOutOfBoundsException if one or more of the indexes in modifierIndexes are lower than 0 or greater
@@ -482,7 +495,8 @@
 		}
 		
 		/**
-		 * Removes all the modifier indexes for a property value at the specified index  
+		 * Removes all the modifier indexes for a property value at the specified index.
+		 *   
 		 * @param propertyValueIndex the index of the property value 
 		 */
 		public void clear(int propertyValueIndex){
@@ -493,7 +507,8 @@
 		/**
 		 * true if this Modifiers object has no types. This can happen if the constructor 
 		 * has been called with an empty or null modifier in the type definition passed as argument 
-		 * to the constructor of the NodeProperties object holding this Modifiers object
+		 * to the constructor of the NodeProperties object holding this Modifiers object.
+		 * 
 		 * @return true if this Modifiers object has no types
 		 */
 		public boolean isNull(){
@@ -517,7 +532,7 @@
 	/**
 	 * A special static instance of the class corresponding to the null NodeProperties. A null NodeProperties instance will have isNull() returning true,
 	 * which means it has no property types associated. 
-	 * @see isNull   
+	 * @see #isNull()   
 	 */
 	public static final NodeProperties NULL_PROPERTIES = new NodeProperties(EMPTY_PROPERTY_TYPE_DEFINITION);
 	
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeReferenceMutableTreeNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeReferenceMutableTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,15 +19,12 @@
 package uk.ac.qmul.eecs.ccmi.diagrammodel;
 
 /**
- * 
- * The diagramModeltreeNode placed in an edge subtree representing a node connected 
+ * The {@code DiagramModeltreeNode} placed in an edge subtree representing a node connected 
  * by the edge itself.
- *
  */
-
 @SuppressWarnings("serial")
-public class NodeReferenceMutableTreeNode extends DiagramModelTreeNode {
-	public NodeReferenceMutableTreeNode(DiagramNode node, DiagramEdge edge){
+public class NodeReferenceMutableTreeNode extends DiagramTreeNode {
+	NodeReferenceMutableTreeNode(DiagramNode node, DiagramEdge edge){
 		super();
 		this.node = node;
 		this.edge = edge;
@@ -55,8 +52,9 @@
 	}
 	
 	/**
-	 * returns the tree node name "as it is", without any decoration such as notes, bookmarks or cardinality.
-	 * Unlike the String returned by toString
+	 * Returns the tree node name "as it is", without any decoration such as notes, bookmarks or cardinality
+	 * ,unlike the String returned by toString.
+	 * 
 	 * @return the tree node name
 	 */
 	@Override
@@ -67,6 +65,7 @@
 	/**
 	 * Returns a String representing this object for this tree node in a way more suitable 
 	 * for a text to speech synthesizer to read, than toString(). 
+	 * 
 	 * @return a String suitable for text to speech synthesis
 	 */
 	@Override
@@ -78,6 +77,7 @@
 	/**
 	 * Returns the diagram edge that has this node in its subtree. Note that diagram edges 
 	 * are DiagramModelTreeNodes as well.
+	 * 
 	 * @return a reference to the diagram edge
 	 */
 	public DiagramEdge getEdge(){
@@ -85,7 +85,8 @@
 	}
 	
 	/**
-	 * Returns the diagram node that this tree node represents inside the edge subtree 
+	 * Returns the diagram node that this tree node represents inside the edge subtree.
+	 * 
 	 * @return a reference to the actual diagram node
 	 */
 	public DiagramNode getNode(){
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyMutableTreeNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyMutableTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -24,13 +24,12 @@
  * @see NodeProperties 
  */
 @SuppressWarnings("serial")
-public class PropertyMutableTreeNode extends DiagramModelTreeNode {
-	public PropertyMutableTreeNode(){
+public class PropertyMutableTreeNode extends DiagramTreeNode {
+	PropertyMutableTreeNode(){
 		super();
 	}
 	
-	
-	public PropertyMutableTreeNode(Object userObject) {
+	PropertyMutableTreeNode(Object userObject) {
 		super(userObject);
 		modifiersString = "";
 	}
@@ -52,7 +51,8 @@
 	
 	/**
 	 * Returns a String representing this object for this tree node in a way more suitable 
-	 * for a text to speech synthesizer to read, than toString(). 
+	 * for a text to speech synthesizer to read, than toString().
+	 * 
 	 * @return a String suitable for text to speech synthesis
 	 */
 	@Override
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyTypeMutableTreeNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyTypeMutableTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -24,24 +24,33 @@
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
 
 /**
- *
- * This DiagramModelTreeNode holds all the PropertyMutableTreeNodes of a certain type in the 
- * tree representation of the diagram.
+ * This {@code DiagramModelTreeNode} holds all the {@code PropertyMutableTreeNode} instances 
+ * of a certain type in the diagram tree.
  *  
  * @see PropertyMutableTreeNode
- *
  */
 @SuppressWarnings("serial")
-public class PropertyTypeMutableTreeNode extends DiagramModelTreeNode {
-	public PropertyTypeMutableTreeNode(String type, DiagramNode n){
+public class PropertyTypeMutableTreeNode extends DiagramTreeNode {
+	PropertyTypeMutableTreeNode(String type, DiagramNode n){
 		setUserObject(type);
 		node = n;
 	}
 	
+	/**
+	 * Returns the property type this tree node is related to.  
+	 * 
+	 * @return a property type string
+	 */
 	public String getType(){
 		return getName();
 	}
 	
+	/**
+	 * Returns a reference to the node that's holding a {@code NodeProperties} with  
+	 * properties of this type.
+	 *  
+	 * @return a reference to a {@code DiagramNode} object
+	 */
 	public DiagramNode getNode(){
 		return node;
 	}
@@ -97,5 +106,4 @@
 	}
 	
 	DiagramNode node;
-	
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TreeModel.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TreeModel.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,8 +19,7 @@
 package uk.ac.qmul.eecs.ccmi.diagrammodel;
 
 import java.util.Set;
-
-import javax.swing.event.ChangeListener;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * 
@@ -35,25 +34,34 @@
 	 * insert a DiagramNode into the diagram model
 	 * 
 	 * @param treeNode the DiagramNode to be inserted in the collection
+	 * @param source the source of the action. This will be reported as the source of the event 
+	 * generated by this action to the registered listeners. If null the TreeModel instance
+	 * itself will be used as source 
 	 * @return true if the model changed as a result of the call
 	 */
-	boolean insertTreeNode(N treeNode);
+	boolean insertTreeNode(N treeNode, Object source);
 	
 	/**
 	 * insert a DiagramEdge into the diagram model
 	 * 
 	 * @param treeNode the DiagramEdge to be inserted in the collection
+	 * @param source the source of the action. This will be reported as the source of the event 
+	 * generated by this action to the registered listeners. If null the TreeModel instance
+	 * itself will be used as source
 	 * @return true if the model changed as a result of the call
 	 */
-	boolean insertTreeNode(E treeNode);
+	boolean insertTreeNode(E treeNode, Object source);
 	
 	/**
 	 * remove a DiagramElement from the model 
 	 * 
 	 * @param treeNode the diagramElement to be removed 
+	 * @param source the source of the action. This will be reported as the source of the event 
+	 * generated by this action to the registered listeners. If null the TreeModel instance
+	 * itself will be used as source
 	 * @return true if the model changed as a result of the call
 	 */
-	boolean takeTreeNodeOut(DiagramElement treeNode);
+	boolean takeTreeNodeOut(DiagramElement treeNode, Object source);
 	
 	/**
 	 * 
@@ -64,14 +72,14 @@
 	 * @return previous value associated with specified key, or null if there was no mapping for key.
 	 * @throws IllegalArgumentException if bookmark is null 
 	 */
-	DiagramModelTreeNode putBookmark(String bookmark, DiagramModelTreeNode treeNode);
+	DiagramTreeNode putBookmark(String bookmark, DiagramTreeNode treeNode, Object source);
 	
 	/**
 	 * Returns a bookmarked tree node
 	 * @param bookmark the bookmark associated with the tree node
 	 * @return the bookmarked tree node or null if no tree node was bookmarked with the argument
 	 */
-	DiagramModelTreeNode getBookmarkedTreeNode(String bookmark);
+	DiagramTreeNode getBookmarkedTreeNode(String bookmark);
 	
 	/**
 	 * Returns the list of all the bookmarks of this tree model
@@ -85,7 +93,7 @@
 	 * @param bookmark the bookmark to remove
 	 * @return previous value associated with specified key, or null if there was no mapping for key.
 	 */
-	DiagramModelTreeNode removeBookmark(String bookmark);
+	DiagramTreeNode removeBookmark(String bookmark, Object source);
 	
 	/**
 	 * Set the notes for the specified tree node. Passing an empty string as notes 
@@ -93,22 +101,24 @@
 	 *  
 	 * @param treeNode the tree node to be noted 
 	 * @param notes the notes to be assigned to the tree node 
+	 * @param source the source of the action. This will be reported as the source of the event 
+	 * generated by this action to the registered listeners  
 	 */
-	void setNotes(DiagramModelTreeNode treeNode, String notes);
+	void setNotes(DiagramTreeNode treeNode, String notes, Object source);
 	
 	/**
-	 * Add a change listener to the model. the listeners will be fired each time the model 
+	 * Add a {@code DiagramNodeListener} to this object. The listeners will be fired each time the model 
 	 * goes from the unmodified to modified state. The model is modified when a either a 
 	 * node or an edge are inserted or removed or changed when they are within the model.    
-	 * @param l a ChangeListener to add to the model  
+	 * @param l a {@code DiagramNodeListener} to add to the model  
 	 */
-	void addChangeListener(ChangeListener l);
+	void addDiagramTreeNodeListener(DiagramTreeNodeListener l);
 	
 	/**
-	 * Removes a change listener from the model. 
-	 * @param l a ChangeListener to remove from the model
+	 * Removes a {@code DiagramNodeListener} from this object. 
+	 * @param l a {@code DiagramNodeListener} to remove from ththis object.
 	 */
-	void removeChangeListener(ChangeListener l);
+	void removeDiagramTreeNodeListener(DiagramTreeNodeListener l);
 	
 	/**
 	 * Returns true if the model has been modified
@@ -124,10 +134,8 @@
 	public void setUnmodified();
 	
 	/**
-	 * Returns an object that can be used to access the nodes and edges (via {@link #getNodes()}
-	 * and {@link #getEdges()} in a synchronized block. The monitor is guaranteed to give 
-	 * exclusive access even in regards to the access via the tree side of the model.  
-	 * @return
+	 * Returns a reentrant lock that can be used to access the nodes and edges in a synchronized fashion.   
+	 * @return a lock object
 	 */
-	Object getMonitor();
+	public ReentrantLock getMonitor();
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TypeMutableTreeNode.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TypeMutableTreeNode.java	Wed Apr 25 17:09:09 2012 +0100
@@ -25,9 +25,8 @@
  *
  */
 @SuppressWarnings("serial")
-public class TypeMutableTreeNode extends DiagramModelTreeNode {
-
-	public TypeMutableTreeNode(DiagramElement element) {
+public class TypeMutableTreeNode extends DiagramTreeNode {
+	TypeMutableTreeNode(DiagramElement element) {
 		super(element.getType());
 		this.prototype = element;
 	}
@@ -66,5 +65,5 @@
 		return builder.toString();
 	}
 	
-	DiagramElement prototype;
+	private DiagramElement prototype;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/AudioFeedback.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,163 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.gui;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNodeListener;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNodeEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent.PropertyChangeArgs;
+import uk.ac.qmul.eecs.ccmi.sound.PlayerListener;
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+
+/**
+ * This class is a listener providing audio (speech + sound) feedback to changes on the 
+ * model (e.g. node added, node removed, node name changed etc.) operated only on the local (so not from 
+ * a tree of another user sharing the same diagram)  
+ * tree it is linked to. If the source of the events is different from the local tree , then no action 
+ * is performed. 
+ */
+public class AudioFeedback implements CollectionListener, DiagramTreeNodeListener  {
+
+	/**
+	 * Construct an {@code AudioFeedback} object linked to a {@code DiagramTree}.
+	 * 
+	 * @param tree the tree this instance is going to be linked to
+	 */
+	AudioFeedback(DiagramTree tree){
+		resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+		this.tree = tree;
+	}
+	
+	@Override
+	public void elementInserted(CollectionEvent e) {
+		DiagramEventSource source = (DiagramEventSource)e.getSource();
+		if(source.isLocal() && source.type == DiagramEventSource.Type.TREE){
+			final DiagramElement diagramElement = e.getDiagramElement();
+			boolean isNode = diagramElement instanceof Node;
+			if(isNode){
+				SoundFactory.getInstance().play( SoundEvent.OK ,new PlayerListener(){
+					@Override
+					public void playEnded() {
+						NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.input.node.ack"),diagramElement.spokenText()));
+					}
+				});
+			}else{
+				Edge edge = (Edge)diagramElement;
+				final StringBuilder builder = new StringBuilder();
+				for(int i=0; i<edge.getNodesNum();i++){
+					if(i == edge.getNodesNum()-1)
+						builder.append(edge.getNodeAt(i)+resources.getString("speech.input.edge.ack"));
+					else
+						builder.append(edge.getNodeAt(i)+ resources.getString("speech.input.edge.ack2"));
+				   	}
+				   	SoundFactory.getInstance().play( SoundEvent.OK, new PlayerListener(){
+				   		@Override
+				   		public void playEnded() {
+				   			NarratorFactory.getInstance().speak(builder.toString());
+				   		}
+				   });
+			}
+		}
+	}
+
+	@Override
+	public void elementTakenOut(CollectionEvent e) {
+		DiagramEventSource source = (DiagramEventSource)e.getSource();
+		if(source.isLocal() && source.type == DiagramEventSource.Type.TREE){
+			final DiagramElement element = e.getDiagramElement();
+			SoundFactory.getInstance().play(SoundEvent.OK, new PlayerListener(){
+				@Override
+				public void playEnded() {
+					NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.delete.element.ack"),element.spokenText(),tree.currentPathSpeech()));
+				}
+			});
+		}
+	}
+
+	@Override
+	public void elementChanged(ElementChangedEvent e) {
+		DiagramEventSource source = (DiagramEventSource)e.getSource();
+		if(!source.isLocal() || source.type != DiagramEventSource.Type.TREE)
+			return;
+		String change = e.getChangeType();
+		if("name".equals(change)){
+			playOK(tree.currentPathSpeech());
+		}else if ("property.add".equals(change)){
+			PropertyChangeArgs args = (PropertyChangeArgs)e.getArguments();
+			String propertyValue = ((Node)e.getDiagramElement()).getProperties().getValues(args.getPropertyType()).get(args.getPropertyIndex());
+			playOK(MessageFormat.format(resources.getString("speech.input.property.ack"),propertyValue));
+		}else if("property.set".equals(change)){
+			playOK(tree.currentPathSpeech());
+		}else if("property.remove".equals(change)){
+			PropertyChangeArgs args = (PropertyChangeArgs)e.getArguments();
+			playOK(MessageFormat.format(resources.getString("speech.deleted.property.ack"),args.getOldValue(),tree.currentPathSpeech()));
+		}else if("property.modifiers".equals(change)){
+			playOK(tree.currentPathSpeech());
+		}else if("arrowHead".equals(change)||"endLabel".equals(change)){
+			playOK(tree.currentPathSpeech());
+		}
+	}
+
+	@Override
+	public void bookmarkAdded(DiagramTreeNodeEvent evt) {
+		DiagramEventSource source = (DiagramEventSource)evt.getSource();
+		if(source.isLocal() && source.type == DiagramEventSource.Type.TREE){
+			playOK(tree.currentPathSpeech());
+		}
+	}
+
+	@Override
+	public void bookmarkRemoved(DiagramTreeNodeEvent evt) {
+		DiagramEventSource source = (DiagramEventSource)evt.getSource();
+		if(source.isLocal() && source.type == DiagramEventSource.Type.TREE){
+			playOK(MessageFormat.format(
+					resources.getString("speech.delete.bookmark.ack"), 
+					evt.getValue(),
+					tree.currentPathSpeech()));
+		}
+	}
+
+	@Override
+	public void notesChanged(DiagramTreeNodeEvent evt) {
+		DiagramEventSource source = (DiagramEventSource)evt.getSource();
+		if(source.isLocal() && source.type == DiagramEventSource.Type.TREE){
+			playOK(tree.currentPathSpeech());
+		}
+	}
+	
+	private void playOK(final String speech){
+		SoundFactory.getInstance().play(SoundEvent.OK, new PlayerListener(){
+			   @Override
+			   public void playEnded() {
+				   NarratorFactory.getInstance().speak(speech);
+			   }
+		   });
+	}
+
+	private ResourceBundle resources;
+	private DiagramTree tree;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,442 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+package uk.ac.qmul.eecs.ccmi.gui;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage;
+import uk.ac.qmul.eecs.ccmi.network.Command;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
+import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
+
+/**
+ * This class provides the two menus to handle nodes and edges on the visual graph. This class 
+ * provides an abstract implementation common to both the node and edge menus. The specific 
+ * implementations are internal static classes, inheriting from this class.
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class CCmIPopupMenu extends JPopupMenu {
+	/**
+	 * This constructor is called by subclasses constructor. 
+	 * 
+	 * @param reference the element this menu refers to (it popped up by right-clicking on it)
+	 * @param parentComponent the component where the menu is going to be displayed
+	 * @param modelUpdater the model updater to make changed to {@code reference}
+	 * @param selectedElements other elements eventually selected on the graph, which are going
+	 * to undergo the same changes as {@code reference}, being selected together with it.  
+	 */
+	protected CCmIPopupMenu(DiagramElement reference,
+			Component parentComponent, DiagramModelUpdater modelUpdater,
+			Set<DiagramElement> selectedElements) {
+		super();
+		this.modelUpdater = modelUpdater;
+		this.parentComponent = parentComponent;
+		this.reference = reference;
+		this.selectedElements = selectedElements;
+	}
+
+	/**
+	 * Returns the the element this menu refers to.
+	 * @return the element this menu refers to.
+	 */
+	public DiagramElement getElement(){
+		return reference;
+	}
+	
+	/**
+	 * Add the a menu item to this menu. A menu item, once clicked on, will prompt the user for a new name
+	 * for the referee element and will execute the update through the modelUpdater passed as argument 
+	 * to the constructor.   
+	 */
+	protected void addNameMenuItem() {
+		/* add set name menu item */
+		JMenuItem setNameMenuItem = new JMenuItem(
+				resources.getString("menu.set_name"));
+		setNameMenuItem.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				String type = (reference instanceof Node) ? "node" : "edge";
+				if (!modelUpdater.getLock(reference, Lock.NAME,
+						new DiagramEventActionSource(DiagramEventSource.GRPH,
+								Command.Name.SET_NODE_NAME, reference.getId(),reference.getName()))) {
+					iLog("Could not get the lock on " + type + " for renaming",
+							DiagramElement.toLogString(reference));
+					JOptionPane.showMessageDialog(parentComponent,
+							MessageFormat.format(resources
+									.getString("dialog.lock_failure.name"),
+									type));
+					return;
+				}
+				iLog("open rename " + type + " dialog",
+						DiagramElement.toLogString(reference));
+				String name = JOptionPane.showInputDialog(parentComponent,
+						MessageFormat.format(
+								resources.getString("dialog.input.name"),
+								reference.getName()), reference.getName());
+				if (name == null)
+					iLog("cancel rename " + type + " dialog",
+							DiagramElement.toLogString(reference));
+				else
+					/* node has been locked at selection time */
+					modelUpdater.setName(reference, name.trim(),
+							DiagramEventSource.GRPH);
+				modelUpdater.yieldLock(reference, Lock.NAME,
+						new DiagramEventActionSource(DiagramEventSource.GRPH,
+								Command.Name.SET_NODE_NAME, reference.getId(),reference.getName()));
+			}
+
+		});
+		add(setNameMenuItem);
+	}
+
+	/**
+	 * Add the a delete item to this menu. A menu item, once clicked on, will prompt the user for a confirmation
+	 * for the deletion of referee element and will execute the update through the modelUpdater passed as argument 
+	 * to the constructor.
+	 */
+	protected void addDeleteMenuItem() {
+		JMenuItem deleteMenuItem = new JMenuItem(resources.getString("menu.delete"));
+		deleteMenuItem.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				/* create a new Set to maintain iterator consistency as elementTakenOut will change selectedItems  */
+				List<DiagramElement> workList = new ArrayList<DiagramElement>(selectedElements); 
+				/* right click on an element with no selection involved */
+				if(workList.isEmpty()){
+					workList.add(reference);
+				/* right click on an element with other elements selected, thus we ignore the *
+				 * currently selected elements and try to delete only the right clicked one.  */
+				}else if(!workList.contains(reference)){
+					workList.clear();
+					workList.add(reference);
+				}else{
+					/* If the right clicked element selected together with other elements, try to         * 
+					 * delete them all. First delete all edges and then all nodes to keep consistency.    *
+					 * We are deleting a bunch of objects and if a node is deleted before an edge         *  
+					 * attached  to it, then an exception will be triggered at the moment of edge         *
+					 * deletion because the edge will be already deleted as a result of the node deletion */
+					Collections.sort(workList, new Comparator<DiagramElement>(){
+						@Override
+						public int compare(DiagramElement e1,DiagramElement e2) {
+							boolean e1isEdge = e1 instanceof Edge;
+							boolean e2isEdge = e2 instanceof Edge;
+							if(e1isEdge && !e2isEdge){
+								return -1;
+							}
+							if(!e1isEdge && e2isEdge){
+								return 1;
+							}
+							return 0;
+						}
+					});
+				}
+					
+				List<DiagramElement>alreadyLockedElements = new ArrayList<DiagramElement>(workList.size());
+				/* check which, of the selected elements, can be deleted and which ones are currently held by     *
+				 * other clients. If an element is locked it's removed from the list and put into a separated set */
+				for(Iterator<DiagramElement> itr=workList.iterator(); itr.hasNext();){
+					DiagramElement  selected = itr.next();
+					boolean isNode = selected instanceof Node;
+					if(!modelUpdater.getLock(selected, 
+							Lock.DELETE, 
+							new DiagramEventActionSource(
+									DiagramEventSource.GRPH, 
+									isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
+									selected.getId(),selected.getName()))){
+						itr.remove();
+						alreadyLockedElements.add(selected);
+					}
+				}
+				
+				/* all the elements are locked by other clients */
+				if(workList.isEmpty()){
+					iLog("Could not get lock on any selected element for deletion","");
+					JOptionPane.showMessageDialog(
+							JOptionPane.getFrameForComponent(parentComponent), 
+							alreadyLockedElements.size() == 1 ? // singular vs plural 
+									resources.getString("dialog.lock_failure.delete") :
+										resources.getString("dialog.lock_failure.deletes"));
+					return;
+				}
+
+				String warning = "";
+				if(!alreadyLockedElements.isEmpty()){
+					StringBuilder builder = new StringBuilder(resources.getString("dialog.lock_failure.deletes_warning"));
+					for(DiagramElement alreadyLocked : alreadyLockedElements)
+						builder.append(alreadyLocked.getName()).append(' ');
+					warning = builder.append('\n').toString();
+					iLog("Could not get lock on some selected element for deletion",warning);
+				}
+
+				iLog("open delete dialog",warning);
+				int answer = JOptionPane.showConfirmDialog(
+						JOptionPane.getFrameForComponent(parentComponent),
+						warning+resources.getString("dialog.confirm.deletions"), 
+						resources.getString("dialog.confirm.title"),
+						SpeechOptionPane.YES_NO_OPTION);
+				if(answer == JOptionPane.YES_OPTION){
+					/* the user chose to delete the elements, proceed (locks       *
+					 * will be automatically removed upon deletion by the server ) */
+					for(DiagramElement selected : workList){
+						modelUpdater.takeOutFromCollection(selected,DiagramEventSource.GRPH);
+						modelUpdater.sendAwarenessMessage(
+								   AwarenessMessage.Name.STOP_A,
+								   new DiagramEventActionSource(DiagramEventSource.TREE,
+										   (selected instanceof Node) ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
+										   selected.getId(),selected.getName())
+								   );
+					}
+				}else{
+					/* the user chose not to delete the elements, release the acquired locks */
+					for(DiagramElement selected : workList){
+						/* if it's a node all its attached edges were locked as well */
+						/*if(selected instanceof Node){ DONE IN THE SERVER
+						Node n = (Node)selected;
+						for(int i=0; i<n.getEdgesNum();i++){
+							modelUpdater.yieldLock(n.getEdgeAt(i), Lock.DELETE);
+						}}*/
+						boolean isNode = selected instanceof Node;
+						modelUpdater.yieldLock(selected, 
+								Lock.DELETE,
+								new DiagramEventActionSource(
+										DiagramEventSource.GRPH, 
+										isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
+												selected.getId(),selected.getName()));
+					}
+					iLog("cancel delete node dialog","");
+				}
+			}
+		});
+		add(deleteMenuItem);
+	}
+
+	/**
+	 * Performs the log in the InteractionLog.
+	 * @param action the action to log.
+	 * @param args additional arguments to add to the log.
+	 * 
+	 * @see uk.ac.qmul.eecs.ccmi.utils#InteractionLog
+	 */
+	protected void iLog(String action, String args) {
+		InteractionLog.log("GRAPH", action, args);
+	}
+	
+	/**
+	 * 
+	 * A popup menu to perform changes (e.g. delete, rename etc.) to a node from the visual graph. 
+	 *
+	 */
+	public static class NodePopupMenu extends CCmIPopupMenu {
+		/**
+		 * 
+		 * @param node the node this menu refers to.
+		 * @param parentComponent the component where the menu is going to be displayed.
+		 * @param modelUpdater the model updater used to make changed to {@code node}.
+		 * @param selectedElements other elements eventually selected on the graph, which are going
+		 * to undergo the same changes as {@code node}, being selected together with it.  
+		 */
+		NodePopupMenu(Node node, Component parentComponent,
+				DiagramModelUpdater modelUpdater,
+				Set<DiagramElement> selectedElements) {
+			super(node, parentComponent, modelUpdater, selectedElements);
+			addNameMenuItem();
+			addPropertyMenuItem();
+			addDeleteMenuItem();
+		}
+
+		private void addPropertyMenuItem() {
+			final Node nodeRef = (Node) reference;
+			/* if the node has no properties defined, then don't add the menu item */
+			if(nodeRef.getProperties().isNull())
+				return;
+			/* add set property menu item*/
+			JMenuItem setPropertiesMenuItem = new JMenuItem(resources.getString("menu.set_properties"));
+			setPropertiesMenuItem.addActionListener(new ActionListener(){
+				@Override
+				public void actionPerformed(ActionEvent e) {
+					if(!modelUpdater.getLock(nodeRef, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName()))){
+						iLog("Could not get the lock on node for properties",DiagramElement.toLogString(nodeRef));
+						JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.properties"));
+						return;
+					}
+					iLog("open edit properties dialog",DiagramElement.toLogString(nodeRef));
+					NodeProperties properties = PropertyEditorDialog.showDialog(JOptionPane.getFrameForComponent(parentComponent),nodeRef.getPropertiesCopy()); 
+					if(properties == null){ // user clicked on cancel 
+						iLog("cancel edit properties dialog",DiagramElement.toLogString(nodeRef));
+						modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName()));
+						return;
+					}
+					if(!properties.isNull())
+						modelUpdater.setProperties(nodeRef,properties,DiagramEventSource.GRPH);
+					modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName()));
+				}
+			});
+			add(setPropertiesMenuItem);
+		}
+	}
+	
+	/**
+	 * A popup menu to perform changes (e.g. delete, rename etc.) to a edge from the visual graph. 
+	 */
+	public static class EdgePopupMenu extends CCmIPopupMenu {
+		/**
+		 * Constructs an {@code EdgePopupMenu} to perform changes to an edge from the visual diagram. 
+		 * This constructor is normally called when the user clicks in the neighbourhood of a node 
+		 * connected to this edge. the menu will then include items to change an end label or 
+		 * an arrow head.  
+		 * @param edge the edge this menu refers to.
+		 * @param node one attached node some menu item will refer to.
+		 * @param parentComponent the component where the menu is going to be displayed.
+		 * @param modelUpdater the model updater used to make changed to {@code edge}.
+		 * @param selectedElements other elements eventually selected on the graph, which are going
+		 * to undergo the same changes as {@code edge}, being selected together with it. 
+		 */
+		public EdgePopupMenu( Edge edge, Node node, Component parentComponent, DiagramModelUpdater modelUpdater,
+				Set<DiagramElement> selectedElements){
+			super(edge,parentComponent,modelUpdater,selectedElements);
+			addNameMenuItem();
+			if(node != null){
+				nodeRef = node;
+				Object[] arrowHeads = new Object[edge.getAvailableEndDescriptions().length + 1];
+				for(int i=0;i<edge.getAvailableEndDescriptions().length;i++){
+					arrowHeads[i] = edge.getAvailableEndDescriptions()[i].toString();
+				}
+				arrowHeads[arrowHeads.length-1] = Edge.NO_ENDDESCRIPTION_STRING;
+				addEndMenuItems(arrowHeads);
+			}
+			addDeleteMenuItem();
+		}
+		
+		/**
+		 * Constructs an {@code EdgePopupMenu} to perform changes to an edge from the visual diagram. 
+		 * This constructor is normally called when the user clicks around the midpoint of the edge 
+		 * @param edge the edge this menu refers to.
+		 * @param parentComponent the component where the menu is going to be displayed.
+		 * @param modelUpdater the model updater used to make changed to {@code edge}.
+		 * @param selectedElements other elements eventually selected on the graph, which are going
+		 * to undergo the same changes as {@code edge}, being selected together with it. 
+		 */
+		public EdgePopupMenu( Edge edge, Component parentComponent, DiagramModelUpdater modelUpdater,
+				Set<DiagramElement> selectedElements){
+			this(edge,null,parentComponent,modelUpdater,selectedElements);
+		}
+		
+		private void addEndMenuItems(final Object[] arrowHeads){
+			final Edge edgeRef = (Edge)reference; 
+			/* Label menu item */
+			JMenuItem setLabelMenuItem = new JMenuItem(resources.getString("menu.set_label"));
+			setLabelMenuItem.addActionListener(new ActionListener(){
+				@Override
+				public void actionPerformed(ActionEvent evt) {
+					if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDLABEL,edgeRef.getId(),reference.getName()))){
+						iLog("Could not get lock on edge for end label",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
+						JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end"));
+						return;
+					}
+					iLog("open edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
+					String text = JOptionPane.showInputDialog(parentComponent,resources.getString("dialog.input.label"));
+					if(text != null)
+						modelUpdater.setEndLabel(edgeRef,nodeRef,text,DiagramEventSource.GRPH);
+					else
+						iLog("cancel edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
+					modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDLABEL,edgeRef.getId(),reference.getName()));
+				}
+			});
+			add(setLabelMenuItem);
+
+			if(arrowHeads.length > 1){
+				/* arrow head menu item */
+				JMenuItem selectArrowHeadMenuItem = new JMenuItem(resources.getString("menu.choose_arrow_head"));
+				selectArrowHeadMenuItem.addActionListener(new ActionListener(){
+					@Override
+					public void actionPerformed(ActionEvent e) {
+						if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()))){
+							iLog("Could not get lock on edge for arrow head",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
+							JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end"));
+							return;
+						}
+						iLog("open select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
+						String arrowHead = (String)JOptionPane.showInputDialog(
+								parentComponent, 
+								resources.getString("dialog.input.arrow"), 
+								resources.getString("dialog.input.arrow.title"), 
+								JOptionPane.PLAIN_MESSAGE, 
+								null, 
+								arrowHeads, 
+								arrowHeads);
+						if(arrowHead == null){
+							iLog("cancel select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
+							modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()));
+							return;
+						}
+						for(int i=0; i<edgeRef.getAvailableEndDescriptions().length;i++){
+							if(edgeRef.getAvailableEndDescriptions()[i].toString().equals(arrowHead)){
+								modelUpdater.setEndDescription(edgeRef, nodeRef, i,DiagramEventSource.GRPH);
+								modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()));
+								return;
+							}
+						}
+						/* the user selected the none menu item */
+						modelUpdater.setEndDescription(edgeRef,nodeRef, Edge.NO_END_DESCRIPTION_INDEX,DiagramEventSource.GRPH);
+						modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()));
+					}
+				});
+				add(selectArrowHeadMenuItem);
+			}
+		}
+		
+		private Node nodeRef;
+	}
+
+	/**
+	 * the model updater used to make changed to {@code reference}.
+	 */
+	protected DiagramModelUpdater modelUpdater;
+	/**
+	 * the component where the menu is going to be displayed.
+	 */
+	protected Component parentComponent;
+	/**
+	 * the element this menu refers to.
+	 */
+	protected DiagramElement reference;
+	/**
+	 * other elements eventually selected on the graph, which are going
+	 * to undergo the same changes as {@code reference}, being selected together with it.
+	 */
+	protected Set<DiagramElement> selectedElements;
+	private static ResourceBundle resources = ResourceBundle.getBundle(CCmIPopupMenu.class.getName());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.properties	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,27 @@
+
+
+
+dialog.lock_failure.name={0} name is being edited by another user
+dialog.lock_failure.properties=Node properties are being edited by another user
+dialog.input.name=Renaming {0}, Enter new name
+
+
+dialog.lock_failure.delete=Object is being edited by another user
+dialog.lock_failure.deletes=Objects are being edited by other users
+dialog.lock_failure.deletes_warning=The following objects will not be deleted as they're locked by other users:
+dialog.confirm.deletions=Are you sure you want to delete the selected objects ?
+dialog.confirm.title=Confirm
+
+menu.set_name=Set Name
+menu.set_properties=Set Properties
+menu.delete=Delete
+menu.set_label=Set Label
+menu.choose_arrow_head=Set Arrow Head
+
+dialog.lock_failure.end=Edge end is being edited by another user
+dialog.lock_failure.name=Edge name is being edited by another user
+
+dialog.input.label=Enter Label Text
+dialog.input.arrow=Choose Arrow Head
+dialog.input.arrow.title=Select
+dialog.input.name=Renaming {0}, Enter new name 
\ No newline at end of file
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Diagram.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Diagram.java	Wed Apr 25 17:09:09 2012 +0100
@@ -24,40 +24,113 @@
 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModel;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
 import uk.ac.qmul.eecs.ccmi.gui.persistence.PrototypePersistenceDelegate;
+import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
 
 /**
- * This Diagram class holds all the data needed for a representation of the diagram. It is used by component classes 
+ * The {@code Diagram} class holds all the data needed for a representation of the diagram. It is used by component classes 
  * such as {@link GraphPanel} and {@link DiagramTree} to draw the diagram by accessing the diagram model or by 
  * {@link EditorTabbedPane} to assign a title to the tabs out of the diagram name. 
- * 
  *
  */
 public abstract class Diagram implements Cloneable {
 	
+	/**
+	 * Crates a new instance of a Diagram. The diagram created through this method is not shared with any peer via 
+	 * a server. 
+	 * @param name the name of the diagram.
+	 * @param nodes an array of node prototypes. Nodes inserted by users in the diagram will be created by cloning these nodes. 
+	 * @param edges an array of edge prototypes. Edges inserted by users in the diagram will be created by cloning these edges.
+	 * @param prototypePersistenceDelegate a delegate class to handle nodes and edges persistence.
+	 * @return a new instance of {@code Diagram}
+	 */
 	public static Diagram newInstance(String name, Node[] nodes, Edge[] edges, PrototypePersistenceDelegate prototypePersistenceDelegate){
 		return new LocalDiagram(name,nodes,edges,prototypePersistenceDelegate);
 	}
 	
+	/**
+	 * Returns the name of the diagram. The name identifies the diagram uniquely in the editor. There cannot
+	 * be two diagrams with the same name open at the same time. This makes things easier when sharing diagrams
+	 * with other users via the network.
+	 * 
+	 * @return the name of the diagram
+	 */
 	public abstract String getName();
 
+	/**
+	 * Assign this diagram a new name. 
+	 * @param name the new name of the diagram
+	 */
 	public abstract void setName(String name);
 
+	/**
+	 * Returns an array with the node prototypes. Node prototypes are used when creating new node 
+	 * instances via the {@code clone()} method. 
+	 * 
+	 * @return an array of nodes 
+	 */
 	public abstract Node[] getNodePrototypes();
 
+	/**
+	 * Returns an array with the edge prototypes. Edge prototypes are used when creating new edge 
+	 * instances via the {@code clone()} method.
+	 * 
+	 * @return an array of edges
+	 */
 	public abstract Edge[] getEdgePrototypes();
 
+	/**
+	 * Returns the tree model of this diagram. Note that each diagram holds a {@code DiagramModel} 
+	 * which has  two sub-models ({@code TreeModel} and {@code CollectionModel}). Changes on one 
+	 * sub-model will affect the other model as well. 
+	 * 
+	 * @return the tree model of this diagram 
+	 * 
+	 * @see uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModel uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModel
+	 */
 	public abstract TreeModel<Node,Edge> getTreeModel();
 
+	/**
+	 * Returns the collection model of this diagram. Note that each diagram holds a {@code DiagramModel} 
+	 * which has  two sub-models ({@code TreeModel} and {@code CollectionModel}). Changes on one 
+	 * sub-model will affect the other model as well. 
+	 * 
+	 * @return the tree model of this diagram 
+	 * 
+	 * @see uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModel uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModel
+	 */
 	public abstract CollectionModel<Node,Edge> getCollectionModel();
 
+	/**
+	 * Returns the model updater of this diagram. The model updater is the delegate for all the 
+	 * update operations affecting the diagram model.
+	 * 
+	 * @return the model updater for this diagram
+	 */
 	public abstract DiagramModelUpdater getModelUpdater();
 	
+	/**
+	 * Returns the label of the diagram. The label is slightly different from the name as it's the string 
+	 * appearing in the tabbed pane of the editor. It includes asterisk character at the end when the {@code DiagramModel}
+	 * of this class has been changed and not  yet saved on hard disk.   
+	 *  
+	 * @return a label for this diagram
+	 */
 	public abstract String getLabel();
 	
+	/**
+	 * Returns the delegates for this diagram for nodes and edges prototypes persistence. 
+	 * When saving a diagram to an xml file each node and edge of the prototypes is encoded  
+	 * in the xml file. Indeed the template of a diagram is made of of its prototypes. 
+	 * In the template is held the general attributes common to all the nodes and edges, like 
+	 * for instance the type of a node but not its current position. 
+	 *  
+	 * @return the PrototypePersistenceDelegate for this diagram 
+	 */
 	public abstract PrototypePersistenceDelegate getPrototypePersistenceDelegate();
 
 	@Override
@@ -130,6 +203,9 @@
 			return prototypePersistenceDelegate;
 		}
 		
+		/**
+		 * Creates a new {@code Diagram} by clonation.  
+		 */
 		@Override
 		public Object clone(){
 			LocalDiagram clone = (LocalDiagram)super.clone();
@@ -152,111 +228,114 @@
 		private class InnerModelUpdater implements DiagramModelUpdater {
 
 			@Override
-			public boolean getLock(DiagramModelTreeNode treeNode, Lock lock) {
+			public boolean getLock(DiagramTreeNode treeNode, Lock lock, DiagramEventActionSource source) {
 				/* using a non shared diagram requires no actual lock, therefore the answer is always yes */
 				return true;
 			}
 			
 			@Override
-			public void yieldLock(DiagramModelTreeNode treeNode, Lock lock) {}
+			public void yieldLock(DiagramTreeNode treeNode, Lock lock, DiagramEventActionSource actionSource) {}
 
+			@Override 
+			public void sendAwarenessMessage(AwarenessMessage.Name awMsgName, Object source){}
+			
 			@Override
-			public void insertInCollection(DiagramElement element) {
+			public void insertInCollection(DiagramElement element,DiagramEventSource source) {
 				if(element instanceof Node)
-					diagramModel.getDiagramCollection().insert((Node)element);
+					diagramModel.getDiagramCollection().insert((Node)element,source);
 				else
-					diagramModel.getDiagramCollection().insert((Edge)element);
+					diagramModel.getDiagramCollection().insert((Edge)element,source);
 			}
 
 			@Override
 			public void insertInTree(DiagramElement element) {
 				if(element instanceof Node)
-					diagramModel.getTreeModel().insertTreeNode((Node)element);
+					diagramModel.getTreeModel().insertTreeNode((Node)element,DiagramEventSource.TREE);
 				else
-					diagramModel.getTreeModel().insertTreeNode((Edge)element);
+					diagramModel.getTreeModel().insertTreeNode((Edge)element,DiagramEventSource.TREE);
 			}
 
 			@Override
-			public void takeOutFromCollection(DiagramElement element) {
-				diagramModel.getDiagramCollection().takeOut(element);
+			public void takeOutFromCollection(DiagramElement element, DiagramEventSource source) {
+				diagramModel.getDiagramCollection().takeOut(element,source);
 			}
 
 			@Override
 			public void takeOutFromTree(DiagramElement element) {
-				diagramModel.getDiagramCollection().takeOut(element);
+				diagramModel.getTreeModel().takeTreeNodeOut(element,DiagramEventSource.TREE);
 			}
 
 			@Override
-			public void setName(DiagramElement element, String name) {
-				element.setName(name);
+			public void setName(DiagramElement element, String name,DiagramEventSource source) {
+				element.setName(name,source);
 			}
 
 			@Override
-			public void setNotes(DiagramModelTreeNode treeNode, String notes) {
-				diagramModel.getTreeModel().setNotes(treeNode, notes);
+			public void setNotes(DiagramTreeNode treeNode, String notes,DiagramEventSource source) {
+				diagramModel.getTreeModel().setNotes(treeNode, notes,source);
 			}
 
 			@Override
 			public void setProperty(Node node, String type, int index,
-					String value) {
-				node.setProperty(type, index, value);
+					String value,DiagramEventSource source) {
+				node.setProperty(type, index, value,source);
 			}
 
 			@Override
-			public void setProperties(Node node, NodeProperties properties) {
-				node.setProperties(properties);
+			public void setProperties(Node node, NodeProperties properties,DiagramEventSource source) {
+				node.setProperties(properties,source);
 			}
 
 			@Override
-			public void clearProperties(Node node) {
-				node.clearProperties();
+			public void clearProperties(Node node,DiagramEventSource source) {
+				node.clearProperties(source);
 			}
 
 			@Override
-			public void addProperty(Node node, String type, String value) {
-				node.addProperty(type, value);
+			public void addProperty(Node node, String type, String value,DiagramEventSource source) {
+				node.addProperty(type, value,source);
 			}
 
 			@Override
-			public void removeProperty(Node node, String type, int index) {
-				node.removeProperty(type, index);
+			public void removeProperty(Node node, String type, int index,DiagramEventSource source) {
+				node.removeProperty(type, index,source);
 			}
 
 			@Override
 			public void setModifiers(Node node, String type, int index,
-					Set<Integer> modifiers) {
-				node.setModifierIndexes(type, index, modifiers);				
+					Set<Integer> modifiers,DiagramEventSource source) {
+				node.setModifierIndexes(type, index, modifiers,source);				
 			}
 
 			@Override
-			public void setEndLabel(Edge edge, Node node, String label) {
-				edge.setEndLabel(node, label);
+			public void setEndLabel(Edge edge, Node node, String label,DiagramEventSource source) {
+				edge.setEndLabel(node, label,source);
 			}
 
 			@Override
 			public void setEndDescription(Edge edge, Node node,
-					int index) {
-				edge.setEndDescription(node, index);
+					int index,DiagramEventSource source) {
+				edge.setEndDescription(node, index,source);
 			}
 
 			@Override
-			public void translate(GraphElement ge, Point2D p, double x, double y) {
-				ge.translate(p, x, y);
+			public void translate(GraphElement ge, Point2D p, double x, double y,DiagramEventSource source) {
+				ge.translate(p, x, y,source);
 			}
 
 			@Override
-			public void startMove(GraphElement ge, Point2D p) {
-				ge.startMove(p);
+			public void startMove(GraphElement ge, Point2D p,DiagramEventSource source) {
+				ge.startMove(p,source);
 			}
 
 			@Override
-			public void bend(Edge edge, Point2D p) {
-				edge.bend(p);
+			public void bend(Edge edge, Point2D p,DiagramEventSource source) {
+				edge.bend(p,source);
 			}
 
 			@Override
-			public void stopMove(GraphElement ge) {
-				ge.stopMove();
+			public void stopMove(GraphElement ge,DiagramEventSource source) {
+				ge.stopMove(source);
 			}
 		}
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramEventSource.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,148 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.gui;
+
+/**
+ * This class identifies the source of a diagram event, that is an event generated by an action
+ * in the diagram such as for instance a node insertion, deletion or renaming. The class carries informations
+ * about how the event was generated (from the tree, from the graph etc.) and if the event was 
+ * generated by the local user or another user co-editing the diagram. In either case an id of the
+ * user is conveyed as well. An id is just a String each user assigns to themselves through a user
+ * interface panel.  
+ */
+public class DiagramEventSource {
+	/**
+	 * An enumeration of the different ways an event can be generated. NONE is for when the 
+	 * information is not relevant (as normally no event listener will be triggered by the event).
+	 * PERS is for actions triggered when rebuilding a diagram from a ccmi file, so not as a consequence
+	 * of a direct user action. 
+	 */
+	public static enum Type{
+		TREE,
+		GRPH, // graph
+		HAPT, // haptics 
+		PERS, // persistence
+		NONE; 
+	}
+	
+	/* constructor used only by the static event sources */
+	private DiagramEventSource(Type type){
+		this.type = type;
+		local = true;
+	}
+	
+	/**
+	 * Creates a new DiagramEventSource out of a previous one (normally one of the static default sources).
+	 * The type of the new object will be the same as the one passed as argument. Object created through
+	 * this constructor are marked as non local 
+	 * 
+	 * @see #isLocal()
+	 * @param eventSource an instance of this class 
+	 */
+	public DiagramEventSource(DiagramEventSource eventSource){
+		this.type = eventSource.type;
+		local = false;
+	}
+
+	/**
+	 * Returns true if the event is local, that is it's has been generated from an action of 
+	 * the local user and not from a message coming from the server. 
+	 * 
+	 * @return {@code true} if the event has been generated by the local user 
+	 */
+	public boolean isLocal(){
+		return local;
+	}
+	
+	/**
+	 * Returns a copy of this event source that is marked as local.  
+	 *   
+	 * @return a local copy of this event source 
+	 * @see #isLocal()
+	 */
+	public DiagramEventSource getLocalSource(){
+		return new DiagramEventSource(type);
+	}
+	
+	/**
+	 * Returns the name of the diagram where the event happened, that has this 
+	 * object as source.  
+	 * 
+	 * @return the name of the diagram
+	 */
+	public String getDiagramName(){
+		return diagramName;
+	}
+	
+	/**
+	 * Sets the name of the diagram where the event happened, that has this 
+	 * object as source.
+	 * 
+	 * @param diagramName the name of the diagram 
+	 */
+	public void setDiagramName(String diagramName){
+		this.diagramName = diagramName;
+	}
+	
+	/**
+	 * The String representation of this object is the concatenation of the type
+	 * and the ID.
+	 * 
+	 * @return a String representing this object  
+	 */
+	@Override
+	public String toString(){
+		return (local ? ISLOCAL_CHAR : ISNOTLOCAL_CHAR )+type.toString();
+	}
+	
+	/**
+	 * Returns an instance of this class out of a string representation, such as 
+	 * returned by {@code toString()}
+	 * @param s a String representation of a {@code DiagramEventSource} instance, such as 
+	 * returned by {@code toString()}
+	 * @return a new instance of {@code DiagramEventSource} 
+	 */
+	public static DiagramEventSource valueOf(String s){
+		Type t = Type.valueOf(s.substring(1, 5));
+		DiagramEventSource toReturn = new DiagramEventSource(t);
+		toReturn.local = (s.charAt(0) == ISLOCAL_CHAR) ? true : false;
+		return toReturn;
+	}
+	
+	private boolean local;
+	public final Type type;
+	private String diagramName;
+	private static char ISLOCAL_CHAR = 'L';
+	private static char ISNOTLOCAL_CHAR = 'R';
+	
+	/** Source for events triggered by the local user through the tree. These static sources
+	 *  are used when the diagram is not shared with any other user. When it is, a new DiagramEventSource
+	 *  will be created, which includes informations about the id and locality of the user who generated the event
+	 */
+	public static DiagramEventSource TREE = new DiagramEventSource(Type.TREE);
+	/** Source for events triggered by the local user through the graph */
+	public static DiagramEventSource GRPH = new DiagramEventSource(Type.GRPH);
+	/** Source for events triggered by the local user through the haptic device  */
+	public static DiagramEventSource HAPT = new DiagramEventSource(Type.HAPT);
+	/** Source for events triggered by the local user when opening a file */
+	public static DiagramEventSource PERS = new DiagramEventSource(Type.PERS);
+	/** Source for events not relevant to model listeners */
+	public static DiagramEventSource NONE = new DiagramEventSource(Type.NONE);
+
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramModelUpdater.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramModelUpdater.java	Wed Apr 25 17:09:09 2012 +0100
@@ -22,8 +22,11 @@
 import java.util.Set;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
+import uk.ac.qmul.eecs.ccmi.network.NetDiagram;
 
 /**
  * 
@@ -38,43 +41,220 @@
  */
 public interface DiagramModelUpdater {
 	
-	public boolean getLock(DiagramModelTreeNode treeNode, Lock lock);
+	/**
+	 * Issues a lock request to the server for the specified tree node.
+	 * 
+	 * @param treeNode the tree node the lock is being requested for
+	 * @param lock the type of lock being requested
+	 * @param actionSource The {@code DiagramEventActionSource} that's going to be piggybacked 
+	 *  on the lock message, for awareness purposes 
+	 * @return {@code true} if the lock is successfully granted by the server
+	 */
+	public boolean getLock(DiagramTreeNode treeNode, Lock lock, DiagramEventActionSource actionSource);
 	
-	public void yieldLock(DiagramModelTreeNode treeNode, Lock lock);
+	/**
+	 * Releases a lock previously acquired by this client. 
+	 *  
+	 * @param treeNode the tree node the lock is being released for
+	 * @param lock the type of lock being released
+	 * @param actionSource The {@code DiagramEventActionSource} that's going to be piggybacked 
+	 *  on the lock message, for awareness purposes.
+	 *  
+	 * @see uk.ac.qmul.eecs.ccmi.network.AwarenessMessage
+	 */
+	public void yieldLock(DiagramTreeNode treeNode, Lock lock ,DiagramEventActionSource actionSource);
 	
-	public void insertInCollection(DiagramElement element);
+	/**
+	 * Sends an awareness message to the server. 
+	 * 
+	 * @param awMsgName the type of awareness message being sent 
+	 * @param source the source of the action. Represents informations to be carried on this message.
+	 * 
+	 * @see uk.ac.qmul.eecs.ccmi.network.AwarenessMessage 
+	 */
+	public void sendAwarenessMessage(AwarenessMessage.Name awMsgName, Object source);
 	
+	/**
+	 * Inserts a {@code DiagramElement} in the {@code CollectionModel} of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param element the element to insert 
+	 * @param source the source of the insertion action. it can be used by collection listeners.   
+	 */
+	public void insertInCollection(DiagramElement element,DiagramEventSource source);
+	
+	/**
+	 * Inserts a {@code DiagramElement} in the {@code TreeModel} of the {@code Diagram} holding this 
+	 * model updater.
+	 * 
+	 * @param element the element to insert 
+	 */
 	public void insertInTree(DiagramElement element);
 	
-	public void takeOutFromCollection(DiagramElement element);
+	/**
+	 * Removes an element from the {@code CollectionModel} of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param element the element to remove 
+	 * @param source the source of the insertion action. it can be used by collection listeners.   
+	 */
+	public void takeOutFromCollection(DiagramElement element,DiagramEventSource source);
 	
+	/**
+	 * Removes an element from the {@code TreeModel} of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param element the element to remove 
+	 */
 	public void takeOutFromTree(DiagramElement element);
-		
-	public void setName(DiagramElement element, String name);
 	
-	public void setProperty(Node node, String type, int index, String value);
+	/**
+	 * Sets a new name for the element of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param element the element being renamed
+	 * @param name the new name 
+	 * @param source the source of the removal action. it can be used by collection listeners.
+	 */
+	public void setName(DiagramElement element, String name,DiagramEventSource source);
 	
-	public void setProperties(Node node, NodeProperties properties);
+	/**
+	 * Sets to a new value a property of a node of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param node the node being set a new property 
+	 * @param type the type of the new property 
+	 * @param index the index of the property being set a new value  
+	 * @param value the new value for the property 
+	 * @param source source the source of the {@code setName} action. it can be used by collection listeners.
+	 */
+	public void setProperty(Node node, String type, int index, String value,DiagramEventSource source);
 	
-	public void clearProperties(Node node);
+	/**
+	 * Replace the whole {@code NodeProperties} object of a node of the {@code Diagram} holding this 
+	 * model updater with a new one. 
+	 * 
+	 * @param node the node being set a new {@code NodeProperties} instance
+	 * @param properties the new {@code NodeProperties} instance 
+	 * @param source source the source of the {@code setProperty} action. it can be used by collection listeners.
+	 */
+	public void setProperties(Node node, NodeProperties properties,DiagramEventSource source);
 	
-	public void setNotes(DiagramModelTreeNode treeNode, String notes);
+	/**
+	 * Clears the properties of a node of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param node the node whose properties are being cleared 
+	 * @param source the source of the {@code setProperties} action. it can be used by collection listeners.
+	 * 
+	 * @see uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties#clear()
+	 */
+	public void clearProperties(Node node,DiagramEventSource source);
+
+	/**
+	 * Set the notes for a tree node of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param treeNode the tree node whose notes are being set 
+	 * @param notes the new notes 
+	 * @param source the source of the {@code setNotes} action. it can be used by collection listeners.
+	 */
+	public void setNotes(DiagramTreeNode treeNode, String notes,DiagramEventSource source);
 	
-	public void addProperty(Node node, String type, String value);
+	/**
+	 * Add a new property to a node's properties of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param node the node whose properties are being added to 
+	 * @param type the type of the new property being added 
+	 * @param value the value of the new property being added 
+	 * @param source the source of the {@code setProperty} action. it can be used by collection listeners.
+	 */
+	public void addProperty(Node node, String type, String value,DiagramEventSource source);
 	
-	public void removeProperty(Node node, String type, int index);
+	/**
+	 * Removes a property from a node's properties of the {@code Diagram} holding this 
+	 * model updater.  
+	 * 
+	 * @param node the node whose properties are being removed from
+	 * @param type the type of the new property being removed
+	 * @param index the index of the property being removed 
+	 * @param source the source of the {@code removeProperty} action. it can be used by collection listeners.
+	 */
+	public void removeProperty(Node node, String type, int index,DiagramEventSource source);
 	
-	public void setModifiers(Node node, String type, int index, Set<Integer> modifiers);
+	/**
+	 * Set the modifiers for a property of a node in of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param node the node whose properties whose modifiers are being 
+	 * @param type the type of the property whose modifiers are being set
+	 * @param index the index of the property whose modifiers are being set
+	 * @param modifiers the new modifiers indexes. the indexes refer to the modifiers type array. 
+	 * @param source the source of the {@code setModifiers} action. it can be used by collection listeners.
+	 */ 
+	public void setModifiers(Node node, String type, int index, Set<Integer> modifiers,DiagramEventSource source);
 	
-	public void setEndLabel(Edge edge, Node node, String label);
+	/**
+	 * Set the end label for an edge of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param edge the edge whose label is being set 
+	 * @param node the node at the edge end where the label is being set 
+	 * @param label the new label 
+	 * @param source the source of the {@code setLabel} action. it can be used by collection listeners.
+	 */
+	public void setEndLabel(Edge edge, Node node, String label,DiagramEventSource source);
 	
-	public void setEndDescription(Edge edge, Node node, int i);// String description);
+	/**
+	 * Set the end description for an edge of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param edge the edge whose end description is being set 
+	 * @param node the node at the edge end where the end description is being set 
+	 * @param index the index of the new end description in the end description array of {@code edge}
+	 * @param source the source of the {@code setEndDescription} action. it can be used by collection listeners.
+	 */
+	public void setEndDescription(Edge edge, Node node, int index, DiagramEventSource source);
 	
-	public void translate(GraphElement ge, Point2D p, double x, double y);
+	/**
+	 * Translates a graph element of the {@code Diagram} holding this 
+	 * model updater. 
+	 * 
+	 * @param ge the graph element being translated 
+	 * @param p the starting point of the translation 
+	 * @param x the distance to translate along the x-axis
+	 * @param y the distance to translate along the y-axis
+	 * @param source the source of the {@code translate} action. it can be used by collection listeners.
+	 */
+	public void translate(GraphElement ge, Point2D p, double x, double y,DiagramEventSource source);
 	
-	public void startMove(GraphElement ge, Point2D p);
+	/**
+	 * Starts the move for a graph element of the {@code Diagram} holding this 
+	 * model updater. The move can be either a translation or a bend (if {@code ge} is an Edge). 
+	 * This method must be called before such motion methods are called in turn.  
+	 * 
+	 * @param ge the graph element being translated 
+	 * @param p the starting point of the motion 
+	 * @param source the source of the {@code startMove} action. it can be used by collection listeners.
+	 */
+	public void startMove(GraphElement ge, Point2D p, DiagramEventSource source);
 	
-	public void bend(Edge edge, Point2D p);
+	/**
+	 * Bends an edge of the {@code Diagram} holding this  model updater.
+	 * 
+	 * @param edge the edge being bended 
+	 * @param p the starting point of the motion 
+	 * @param source the source of the {@code bend} action. it can be used by collection listeners.
+	 */
+	public void bend(Edge edge, Point2D p,DiagramEventSource source);
 	
-	public void stopMove(GraphElement ge);
+	/**
+	 * Finishes off the motion of a graph element of the {@code Diagram} holding this 
+	 * model updater. 
+	 *  
+	 * @param ge the graph element being moved
+	 * @param sourcethe source of the {@code stopMove} action. it can be used by collection listeners.
+	 */
+	public void stopMove(GraphElement ge,DiagramEventSource source);
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramPanel.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramPanel.java	Wed Apr 25 17:09:09 2012 +0100
@@ -1,6 +1,6 @@
 /*  
  CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-  
+
  Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
  Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
 
@@ -16,11 +16,12 @@
 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+ */
 
 package uk.ac.qmul.eecs.ccmi.gui;
 
 import java.awt.BorderLayout;
+import java.io.IOException;
 
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
@@ -28,105 +29,270 @@
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 
+import uk.ac.qmul.eecs.ccmi.gui.awareness.AwarenessPanel;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter;
+import uk.ac.qmul.eecs.ccmi.network.NetDiagram;
+
 /**
  * It's the panel which displays a diagram. It contains a {@link GraphPanel}, a {@link DiagramTree} 
- * and a {@link GraphToolbar} 
- *
+ * a {@link GraphToolbar} and the {@code AwarenessPanel}.
+ * It's backed up by an instance of {@code Diagram}.
  */
 @SuppressWarnings("serial")
 public class DiagramPanel extends JPanel{
-   
-   public DiagramPanel(Diagram diagram, EditorTabbedPane tabbedPane){
-	  this.diagram = diagram;
-	  this.tabbedPane = tabbedPane;
-	  setName(diagram.getLabel());
-	  setLayout(new BorderLayout());
-	  
-	  modelChangeListener = new ChangeListener(){
-		  @Override
-		  public void stateChanged(ChangeEvent e) {
-			  setModified(true);
-		  }
-	  };
-	  
-      toolbar = new GraphToolbar(diagram);
-      graphPanel = new GraphPanel(diagram, toolbar);
-      /* the focus must be hold by the tree and the tab panel only */
-      toolbar.setFocusable(false);
-      graphPanel.setFocusable(false);
-      
-      tree = new DiagramTree(diagram);
-      
-      /* the panel containing the graph and the toolbar */
-      JPanel graphAndToolbarPanel = new JPanel(new BorderLayout());
-      graphAndToolbarPanel.add(toolbar, BorderLayout.NORTH);
-      graphAndToolbarPanel.add(new JScrollPane(graphPanel),BorderLayout.CENTER);
-      
-      JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
-            new JScrollPane(tree),
-      		graphAndToolbarPanel);
-      splitPane.setDividerLocation((int)tree.getPreferredSize().width*2);
-      add(splitPane, BorderLayout.CENTER);
-      
-      diagram.getCollectionModel().addChangeListener(modelChangeListener);
-   }
 
-   public String getFilePath(){
-      return filePath;
-   }
+	/**
+	 * Creates a new instance of {@code DiagramPanel} holding the diagram passed as argument. 
+	 * 	
+	 * @param diagram the diagram this panel is backed up by
+	 * @param tabbedPane the tabbed pane this DiagramPanel will be added to. This reference 
+	 * is used to updated the tab label when the diagram is modified or save (in the former
+	 * case a star is added to the label, in the latter case the star is removed)
+	 */
+	public DiagramPanel(Diagram diagram, EditorTabbedPane tabbedPane){
+		this.diagram = diagram;
+		this.tabbedPane = tabbedPane;
 
-   public void setFilePath(String newValue){
-      filePath = newValue;
-   }
-   
-   public Diagram getDiagram(){
-	   return diagram;
-   }
-   
-   public void setDiagram(Diagram diagram){
-	   /* remove the listener from the old model  */
-	   this.diagram.getCollectionModel().removeChangeListener(modelChangeListener);
-	   diagram.getCollectionModel().addChangeListener(modelChangeListener);
-	   
-	   this.diagram = diagram;
-	   tree.setDiagram(diagram);
-	   graphPanel.setModelUpdater(diagram.getModelUpdater());
-	   setName(diagram.getLabel());
-	   /* set the * according to the new diagram's model modification status */
-	   setModified(isModified());
-   }
-   
-   public GraphPanel getGraphPanel(){
-	   return graphPanel;
-   }
-   
-   public DiagramTree getTree(){
-	   return tree;
-   }
-   
-   /** This method is for changing the 'modified' status of the diagram.       * 
-    * When called passing false as argument listeners are notified that the   *
-    * diagram has been saved. 											      */
-   public void setModified(boolean modified){
-	   if(!modified)
-		   diagram.getCollectionModel().setUnmodified();
-	   /* add an asterisk to notify that the diagram has changed */
-       if(modified)
-     	  setName(getName()+"*");
-       else 
-    	   setName(diagram.getLabel());
-       tabbedPane.refreshComponentTabTitle(this);
-   }
-   
-   public boolean isModified(){
-	   return diagram.getCollectionModel().isModified();
-   }
+		setName(diagram.getLabel());
+		setLayout(new BorderLayout());
 
-   private Diagram diagram;
-   private GraphPanel graphPanel;
-   private DiagramTree tree;
-   private GraphToolbar toolbar;
-   private String filePath;
-   private ChangeListener modelChangeListener;
-   private EditorTabbedPane tabbedPane;
+		modelChangeListener = new ChangeListener(){
+			@Override
+			public void stateChanged(ChangeEvent e) {
+				setModified(true);
+			}
+		};
+
+		toolbar = new GraphToolbar(diagram);
+		graphPanel = new GraphPanel(diagram, toolbar);
+		/* the focus must be hold by the tree and the tab panel only */
+		toolbar.setFocusable(false);
+		graphPanel.setFocusable(false);
+
+		tree = new DiagramTree(diagram);
+
+		/* the panel containing the graph and the toolbar and the awareness panel */
+		visualPanel = new JPanel(new BorderLayout());
+		visualPanel.add(toolbar, BorderLayout.NORTH);
+		visualPanel.add(new JScrollPane(graphPanel),BorderLayout.CENTER);
+		awarenessSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+		awarenessSplitPane.setTopComponent(visualPanel);
+
+		/* divides the tree from the visual diagram */
+		JSplitPane treeSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+				new JScrollPane(tree),
+				awarenessSplitPane);
+		treeSplitPane.setDividerLocation((int)tree.getPreferredSize().width*2);
+		add(treeSplitPane, BorderLayout.CENTER);
+		diagram.getCollectionModel().addChangeListener(modelChangeListener);
+	}
+
+	/**
+	 * When a diagram is saved on the file system the its path is associated to the diagram panel 
+	 * and it's shown when the user hover on the its tab title.  
+	 * 
+	 * @return the path of the file where this diagram has been saved last time or {@code null}  
+	 */
+	public String getFilePath(){
+		return filePath;
+	}
+	
+	/**
+	 * Sets the file path to a new path. This method should be called after the backing diagram has
+	 * been saved to a file.
+	 * 
+	 * @param newValue the path of the file where the backing diagram has been saved last time
+	 */
+	public void setFilePath(String newValue){
+		filePath = newValue;
+	}
+
+	/**
+	 * Returns a reference to the backing diagram of this diagram panel.
+	 * 
+	 * @return a reference to the backing diagram of this diagram panel
+	 */
+	public Diagram getDiagram(){
+		return diagram;
+	}
+
+	/**
+	 * Enables or disables the awareness panel of this diagram panel. As default the awareness panel 
+	 * is disabled but if the diagram is shared (either on a local or on a remote server) the awareness 
+	 * panel gets enabled. In fact, from now on, awareness messages will be received from the server and,
+	 * even if the awareness panel is not visible, some messages (username messages) 
+	 * will still have to be taken into account.
+	 *    
+	 * @param enabled {@code true} if the panel is to be enabled, {@code false} otherwise.
+	 */
+	public void setAwarenessPanelEnabled(boolean enabled){
+		if(!(diagram instanceof NetDiagram))
+			return;
+		/* if the display filter has not been created yet, do create it */
+		DisplayFilter filter = DisplayFilter.getInstance();
+		if(filter == null)
+			try{
+				filter = DisplayFilter.createInstance();
+			}catch(IOException ioe){
+				SpeechOptionPane.showMessageDialog(this, ioe.getLocalizedMessage());
+				return;
+			}
+
+			NetDiagram netDiagram = (NetDiagram)diagram;
+			if(enabled){
+				awarenessPanel = new AwarenessPanel(diagram.getName());
+				awarenessPanelScrollPane = new JScrollPane(awarenessPanel);
+				netDiagram.enableAwareness(awarenessPanel);
+				if(awarenessPanelListener != null)
+					awarenessPanelListener.awarenessPanelEnabled(true);
+			}else{ //disabled
+				netDiagram.disableAwareness(awarenessPanel);
+				if(awarenessSplitPane.getRightComponent() != null){
+					// hide the panel
+					awarenessSplitPane.remove(awarenessPanelScrollPane);
+				}
+				awarenessPanelScrollPane = null;
+				awarenessPanel = null;
+				awarenessSplitPane.validate();
+				if(awarenessPanelListener != null)
+					awarenessPanelListener.awarenessPanelEnabled(false);
+			}
+	}
+
+	/**
+	 * Makes the awareness panel visible or invisible, assuming that it has been enabled beforehand. If the 
+	 * awareness panel hasn't been enables this call has no effect.  
+	 * 
+	 * @param visible {@code true} if the panel is to be made visible, {@code false} otherwise.
+	 */
+	public void setAwarenessPanelVisible(boolean visible){
+		if(awarenessPanelScrollPane == null)
+			return;
+		if(visible){
+			awarenessSplitPane.setRightComponent(awarenessPanelScrollPane);
+			awarenessSplitPane.setDividerLocation(0.8);
+			awarenessSplitPane.setResizeWeight(1.0);
+			awarenessSplitPane.validate();
+			if(awarenessPanelListener != null)
+				awarenessPanelListener.awarenessPanelVisible(true);
+		}else{
+			awarenessSplitPane.remove(awarenessPanelScrollPane);
+			awarenessSplitPane.validate();
+			if(awarenessPanelListener != null)
+				awarenessPanelListener.awarenessPanelVisible(false);
+		}
+	}
+
+	/**
+	 * Queries the diagram panel on whether the awareness panel is currently visible.  
+	 * 
+	 * @return {@code true} if the awareness panel is currently visible, {@code false} otherwise. 
+	 */
+	public boolean isAwarenessPanelVisible(){
+		return (awarenessSplitPane.getRightComponent() != null);
+	}
+
+	/**
+	 * Returns a reference to the inner awareness panel. 
+	 * 
+	 * @return the inner awareness panel if it has been enabled of {@code null} otherwise
+	 */
+	public AwarenessPanel getAwarenessPanel(){
+		return awarenessPanel;
+	}
+
+	/**
+	 * Sets the backing diagram for this panel. This method is used when a diagram is shared 
+	 * (or reverted). A shared diagram has a different way of updating the   
+	 * The modified status is changed according to
+	 * the modified status of the {@code DiagramModel} internal to the new {@code Diagram}   
+	 * 
+	 * @param diagram
+	 */
+	public void setDiagram(Diagram diagram){
+		/* remove the listener from the old model  */
+		this.diagram.getCollectionModel().removeChangeListener(modelChangeListener);
+		diagram.getCollectionModel().addChangeListener(modelChangeListener);
+
+		this.diagram = diagram;
+		tree.setDiagram(diagram);
+		graphPanel.setModelUpdater(diagram.getModelUpdater());
+		setName(diagram.getLabel());
+		/* set the * according to the new diagram's model modification status */
+		setModified(isModified());
+	}
+
+	/**
+	 * Returns a reference to the graph panel in this diagram panel. The tree's model is the 
+	 * model returned by a calling {@code getTreeModel()} on the backing diagram.   
+	 * 
+	 * @return the graph panel  contained by this diagram panel 
+	 */
+	public GraphPanel getGraphPanel(){
+		return graphPanel;
+	}
+
+	/**
+	 * Returns a reference to the tree in this diagram panel. The graph model is the 
+	 * model returned by a calling {@code getCollectionModel()} on the backing diagram.   
+	 * 
+	 * @return the tree contained by this diagram panel 
+	 */
+	public DiagramTree getTree(){
+		return tree;
+	}
+
+	/**
+	 * Changes the {@code modified} status of the backing diagram of this panel. If set to {@code true} 
+	 * then a star will appear after the name of the diagram, returned by {@code getName()}. 
+	 * 
+	 * When called passing false as argument (which should be done after the diagram is saved on a file)  
+	 * listeners are notified that the diagram has been saved. 
+	 * 
+	 * @param modified {@code true} when the diagram has been modified, {@code false} when it has been saved 	
+	 */
+	public void setModified(boolean modified){
+		if(!modified)
+			diagram.getCollectionModel().setUnmodified();
+		/* add an asterisk to notify that the diagram has changed */
+		if(modified)
+			setName(getName()+"*");
+		else 
+			setName(diagram.getLabel());
+		tabbedPane.refreshComponentTabTitle(this);
+	}
+
+	/**
+	 * Whether the backing diagram has been modified. The diagram is modified as a result of changes
+	 * to the {@code TreeModel} or {@code CollectionModel} it contains. To change the {@code modified}
+	 * status of the diagram (and of its models) {@code setModified()} must be used. 
+	 * 
+	 * @return
+	 */
+	public boolean isModified(){
+		return diagram.getCollectionModel().isModified();
+	}
+
+	void setAwarenessPanelListener(AwarenessPanelEnablingListener listener){
+		awarenessPanelListener = listener;
+	}
+
+	private Diagram diagram;
+	private GraphPanel graphPanel;
+	private JSplitPane awarenessSplitPane;
+	private DiagramTree tree;
+	private JPanel visualPanel;
+	private GraphToolbar toolbar;
+	private AwarenessPanel awarenessPanel;
+	private JScrollPane awarenessPanelScrollPane;
+	private String filePath;
+	private ChangeListener modelChangeListener;
+	private EditorTabbedPane tabbedPane;
+	private AwarenessPanelEnablingListener awarenessPanelListener;
 }
+
+interface AwarenessPanelEnablingListener {
+	public void awarenessPanelEnabled(boolean enabled);
+	public void awarenessPanelVisible(boolean visible);
+}
+
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java	Wed Apr 25 17:09:09 2012 +0100
@@ -40,12 +40,14 @@
 import javax.swing.tree.TreeSelectionModel;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.TypeMutableTreeNode;
+import uk.ac.qmul.eecs.ccmi.network.Command;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
 import uk.ac.qmul.eecs.ccmi.sound.PlayerListener;
 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
@@ -54,14 +56,28 @@
 import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 
+
 @SuppressWarnings("serial")
 public class DiagramTree extends JTree {
+	/**
+	 * Creates a new diagram tree. The model of this tree is set to the tree model 
+	 * held by the instance of {@code Diagram} passed as argument. The model is retrieved by a call 
+	 * to {@code getTreeModel()} on the diagram.
+	 * <p>
+	 * The tree doesn't allow interaction via the mouse. It can be navigated via the keyboard using 
+	 * the arrow keys. When a node is selected, cursoring up and down allows the user to go through
+	 * all the sibling of the selected node. Cursoring right will expand the selected node (if it has children)
+	 * and select its first child. Cursoring left will collapse a node and select its father. All the motions
+	 * trigger a text to speech utterance (possibly accompanied by sound) about the new selected node.       
+	 * 
+	 * @param diagram a reference to the diagram holding the tree model for this tree. 
+	 */
 	public DiagramTree(Diagram diagram){
 		super(diagram.getTreeModel());
 		this.diagram = diagram;
 		resources = ResourceBundle.getBundle(EditorFrame.class.getName());
 		
-		TreePath rootPath = new TreePath((DiagramModelTreeNode)diagram.getTreeModel().getRoot());
+		TreePath rootPath = new TreePath((DiagramTreeNode)diagram.getTreeModel().getRoot());
 		setSelectionPath(rootPath);
 		collapsePath(rootPath);
 		selectedNodes = new ArrayList<Node>();
@@ -70,7 +86,7 @@
 	    overwriteTreeKeystrokes();
 	    /* don't use the swing focus system as we provide one on our own */
 	    setFocusTraversalKeysEnabled(false);
-	    getAccessibleContext().setAccessibleName("");
+	    getAccessibleContext().setAccessibleName("tree");
 	}
 	
 	@SuppressWarnings("unchecked")
@@ -79,19 +95,30 @@
 		return (TreeModel<Node,Edge>)super.getModel();
 	}
 	
+	/**
+	 * @see javax.swing.JTree#setModel(javax.swing.tree.TreeModel)
+	 * 
+	 * @param newModel the new mnodel for this tree
+	 */
 	public void setModel(TreeModel<Node,Edge> newModel){
-		DiagramModelTreeNode selectedTreeNode = (DiagramModelTreeNode)getSelectionPath().getLastPathComponent();
+		DiagramTreeNode selectedTreeNode = (DiagramTreeNode)getSelectionPath().getLastPathComponent();
 		super.setModel(newModel);
 		collapseRow(0);
 		setSelectionPath(new TreePath(selectedTreeNode.getPath()));
 	}
 	
+	/**
+	 * Set a new diagram for this tree. As a result of this call the tree model 
+	 * of this tree will be set to the model return by {@code diagram.getTreeModel()} 
+	 *	
+	 * @param diagram the new diagram for this tree 
+	 */
 	public void setDiagram(Diagram diagram){
 		this.diagram = diagram;
 		setModel(diagram.getTreeModel());
 	}
 	
-	public void selectNode(final Node n){
+	private void selectNode(final Node n){
 		selectedNodes.add(n);
 		treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
 		
@@ -104,7 +131,7 @@
 		InteractionLog.log(INTERACTIONLOG_SOURCE,"node selected for edge",n.getName());
 	}
 	
-	public void unselectNode(final Node n){
+	private void unselectNode(final Node n){
 		selectedNodes.remove(n);
 		treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
 		
@@ -117,23 +144,41 @@
 		InteractionLog.log(INTERACTIONLOG_SOURCE,"node unselected for edge",DiagramElement.toLogString(n));
 	}
 	
+	/**
+	 * Returns an array containing the references to all the nodes that have so far been selected 
+	 * for edge creation. A new array is created each time this method is called. 
+	 * 
+	 * @return an array of nodes
+	 */
 	public DiagramNode[] getSelectedNodes(){
 		DiagramNode[] array = new DiagramNode[selectedNodes.size()]; 
 		return selectedNodes.toArray(array);
 	}
 	
+	/**
+	 * Makes all the nodes selected for edge creation unselected. This method should 
+	 * be called after an edge has been created, to get the user restart 
+	 * go over the selection process again.   
+	 * 
+	 */
 	public void clearNodeSelections(){
 		ArrayList<Node> tempList = new ArrayList<Node>(selectedNodes);
 		selectedNodes.clear();
 		for(Node n : tempList){
 			treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
-			diagram.getModelUpdater().yieldLock(n, Lock.MUST_EXIST);
+			diagram.getModelUpdater().yieldLock(n, Lock.MUST_EXIST, new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.INSERT_EDGE,n.getId(),n.getName()));
 		}
 	}
 	
+	/**
+	 * Returns a string for a text to speech synthesizer, describing the currently selected 
+	 * tree node. The one that is at the end of the current selection path. 
+	 * 
+	 * @return a description string suitable for text to speech synthesis 
+	 */
 	public String currentPathSpeech(){
 		TreePath path =	getSelectionPath();
-		DiagramModelTreeNode selectedPathTreeNode = (DiagramModelTreeNode)path.getLastPathComponent();
+		DiagramTreeNode selectedPathTreeNode = (DiagramTreeNode)path.getLastPathComponent();
 		if(selectedNodes.contains(selectedPathTreeNode))
 			/* add information about the fact that the node is selected */
 			return MessageFormat.format(resources.getString("speech.node_selected"), selectedPathTreeNode.spokenText());
@@ -142,8 +187,12 @@
 	}
 	
 	/**
-	 * this method changes the selected tree node from an edge/node reference
-	 * to the related edge/node itself
+	 * Changes the selected tree path from the current to one defined by 
+	 * the {@code JumpTo enum} 
+	 * 
+	 * @see JumpTo
+	 * 
+	 * @param jumpTo a {@code JumpTo enum}
 	 */
 	public void jump(JumpTo jumpTo){
 		final Narrator narrator = NarratorFactory.getInstance();
@@ -151,62 +200,62 @@
 		switch(jumpTo){
 		case REFERENCE :
 			oldPath = getSelectionPath();
-			DiagramModelTreeNode selectedTreeNode = (DiagramModelTreeNode)oldPath.getLastPathComponent();
+			DiagramTreeNode selectedTreeNode = (DiagramTreeNode)oldPath.getLastPathComponent();
 			if(selectedTreeNode instanceof NodeReferenceMutableTreeNode){
 				final Node n = (Node)((NodeReferenceMutableTreeNode)selectedTreeNode).getNode();
 				setSelectionPath(new TreePath(n.getPath()));
-				SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
+				SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
 					   @Override
 					   public void playEnded() {
 						   narrator.speak(MessageFormat.format(resources.getString("speech.jump"),n.spokenText()));
 					   }
-				   }, SoundEvent.JUMP);
+				});
 			}else if(selectedTreeNode instanceof EdgeReferenceMutableTreeNode){
 				final Edge e = (Edge)((EdgeReferenceMutableTreeNode)selectedTreeNode).getEdge();
 				setSelectionPath(new TreePath(e.getPath()));
-				SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
+				SoundFactory.getInstance().play(SoundEvent.JUMP,new PlayerListener(){
 					   @Override
 					   public void playEnded() {
 						   narrator.speak(MessageFormat.format(resources.getString("speech.jump"),e.spokenText()));
 					   }
-				   }, SoundEvent.JUMP);
+				   });
 			}
 			/* assume the referee has only root in common with the reference and collapse everything up to the root (excluded) */
-			collapseAll(selectedTreeNode, (DiagramModelTreeNode)selectedTreeNode.getPath()[1]);
+			collapseAll(selectedTreeNode, (DiagramTreeNode)selectedTreeNode.getPath()[1]);
 			break;
 		case ROOT :
-			final DiagramModelTreeNode from =(DiagramModelTreeNode)getSelectionPath().getLastPathComponent();
+			final DiagramTreeNode from =(DiagramTreeNode)getSelectionPath().getLastPathComponent();
 			setSelectionRow(0);
 			collapseAll(from,from.getRoot());
-			SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
+			SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
 				   @Override
 				   public void playEnded() {
 					   narrator.speak(MessageFormat.format(resources.getString("speech.jump"),from.getRoot().spokenText()));
 				   }
-			   }, SoundEvent.JUMP);
+			   });
 			break;
-		case TYPE : // jumps to the ancestor type node of the current node, never used
-			oldPath = getSelectionPath();
-			int index = 0;
-			Object[] pathComponents = oldPath.getPath();
-			for(int i=0;i<pathComponents.length;i++){
-				if(pathComponents[i] instanceof TypeMutableTreeNode){
-					index=i;
-					break;
-				}
-			}
-			final DiagramModelTreeNode typeTreeNode = (DiagramModelTreeNode)oldPath.getPathComponent(index); 
-			setSelectionPath(new TreePath(typeTreeNode.getPath()));
-			collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(),typeTreeNode);
-			SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-				   @Override
-				   public void playEnded() {
-					   narrator.speak(MessageFormat.format(resources.getString("speech.jump"),typeTreeNode.spokenText()));
-				   }
-			   }, SoundEvent.JUMP);
-			break;
+//		case TYPE : // jumps to the ancestor type node of the current node, never used
+//			oldPath = getSelectionPath();
+//			int index = 0;
+//			Object[] pathComponents = oldPath.getPath();
+//			for(int i=0;i<pathComponents.length;i++){
+//				if(pathComponents[i] instanceof TypeMutableTreeNode){
+//					index=i;
+//					break;
+//				}
+//			}
+//			final DiagramTreeNode typeTreeNode = (DiagramTreeNode)oldPath.getPathComponent(index); 
+//			setSelectionPath(new TreePath(typeTreeNode.getPath()));
+//			collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(),typeTreeNode);
+//			SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
+//				   @Override
+//				   public void playEnded() {
+//					   narrator.speak(MessageFormat.format(resources.getString("speech.jump"),typeTreeNode.spokenText()));
+//				   }
+//			   });
+//			break;
 		case SELECTED_TYPE :
-			DiagramModelTreeNode root = (DiagramModelTreeNode)getModel().getRoot();
+			DiagramTreeNode root = (DiagramTreeNode)getModel().getRoot();
 			Object[] types = new Object[root.getChildCount()];
 			for(int i=0; i< root.getChildCount();i++)
 				types[i] = ((TypeMutableTreeNode)root.getChildAt(i)).getName();//not to spokenText as it would be too long
@@ -242,13 +291,13 @@
 			}
 			setSelectionPath(new TreePath(typeNode.getPath()));
 			if(oldPath.getPath().length >= 2)
-				collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(), (DiagramModelTreeNode)initialValue);
-			SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
+				collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(), (DiagramTreeNode)initialValue);
+			SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
 				   @Override
 				   public void playEnded() {
 					   narrator.speak(MessageFormat.format(resources.getString("speech.jump"),selectedValue));
 				   }
-			   }, SoundEvent.JUMP);
+			   });
 			break;
 		case BOOKMARK :
 			TreeModel<Node,Edge> treeModel = getModel(); 
@@ -277,16 +326,16 @@
 
 			if(bookmark != null){
 				oldPath = getSelectionPath();
-				DiagramModelTreeNode treeNode = treeModel.getBookmarkedTreeNode(bookmark);
-				collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(), (DiagramModelTreeNode)treeModel.getRoot());
+				DiagramTreeNode treeNode = treeModel.getBookmarkedTreeNode(bookmark);
+				collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(), (DiagramTreeNode)treeModel.getRoot());
 				setSelectionPath(new TreePath(treeNode.getPath()));
 				final String currentPathSpeech = currentPathSpeech();
-				SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
+				SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
 					   @Override
 					   public void playEnded() {
 						   narrator.speak(currentPathSpeech);
 					   }
-				   }, SoundEvent.JUMP);
+				   });
 				InteractionLog.log(INTERACTIONLOG_SOURCE,"bookmark selected",bookmark);
 			}else{
 				/* it speaks anyway, as we set up the speech in the EditorFrame class. no need to use the narrator then */
@@ -297,13 +346,20 @@
 			break;
 			
 		}
-		InteractionLog.log(INTERACTIONLOG_SOURCE,"jumped to "+jumpTo.toString(),((DiagramModelTreeNode)getSelectionPath().getLastPathComponent()).getName());
-		SoundFactory.getInstance().play(SoundEvent.JUMP);
+		InteractionLog.log(INTERACTIONLOG_SOURCE,"jumped to "+jumpTo.toString(),((DiagramTreeNode)getSelectionPath().getLastPathComponent()).getName());
 	}
 	
+	/**
+	 * Changes the selected tree path from the current to the one from the root
+	 * to the {@code Diagramelement} passed as argument. Note that a {@code Diagramelement}
+	 * is also an instance of {@code DuagramTreeNode} and it's placed in a {@code TreeModel}
+	 * when it's inserted into a {@code DiagramModel}
+	 * 
+	 * @param de the diagram element to be selected on the tree 
+	 */
 	public void jumpTo(final DiagramElement de){
 		TreePath oldPath = getSelectionPath();
-		collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(),de);
+		collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(),de);
 		setSelectionPath(new TreePath(de.getPath()));
 		SoundFactory.getInstance().play( SoundEvent.JUMP, new PlayerListener(){
 			@Override
@@ -314,26 +370,36 @@
 	}
 	
 	/* collapse all the nodes in the path from "from" to "to" upwards(with the same direction as going from a leaf to the root)*/
-	private void collapseAll(DiagramModelTreeNode from, DiagramModelTreeNode to){
-		DiagramModelTreeNode currentNode = from;
+	private void collapseAll(DiagramTreeNode from, DiagramTreeNode to){
+		DiagramTreeNode currentNode = from;
 		while(currentNode.getParent() != null && currentNode != to){
 			currentNode = currentNode.getParent();
 			collapsePath(new TreePath(currentNode.getPath()));
 		}
 	}
 	
+	/**
+	 * Mouse events are ignored by this tree. This is just a blank method.
+	 * 
+	 * @param e a mouse event
+	 */
 	@Override
 	protected  void	processMouseEvent(MouseEvent e){
 		//do nothing as the tree does not have to be editable with mouse
 	}
 	
+	/**
+	 * Allows only cursor keys, tab key, delete, and actions (CTRL+something)
+	 * 
+	 * @param e a key event 
+	 *  
+	 */
 	@Override
 	protected void processKeyEvent(KeyEvent e){
 		/* allow only cursor keys, tab key, delete, and actions (CTRL+something) */
 		if(e.getKeyChar() == KeyEvent.CHAR_UNDEFINED 
 				|| e.getKeyCode() == KeyEvent.VK_TAB
 					|| e.getKeyCode() == KeyEvent.VK_SPACE
-						|| e.getKeyCode() == KeyEvent.VK_DELETE
 				 			|| e.isControlDown()
 				 				|| e.isAltDown())
 			super.processKeyEvent(e);
@@ -348,16 +414,16 @@
 		   getActionMap().put("down", new AbstractAction(){
 				@Override
 				public void actionPerformed(ActionEvent evt) {
-					DiagramModelTreeNode treeNode = (DiagramModelTreeNode)getLastSelectedPathComponent();
+					DiagramTreeNode treeNode = (DiagramTreeNode)getLastSelectedPathComponent();
 					/* look if we've got a sibling node after (we are not at the bottom) */
-					DiagramModelTreeNode nextTreeNode = treeNode.getNextSibling(); 
+					DiagramTreeNode nextTreeNode = treeNode.getNextSibling(); 
 					SoundEvent loop = null;
 					if(nextTreeNode == null){
-						DiagramModelTreeNode parent = treeNode.getParent();
+						DiagramTreeNode parent = treeNode.getParent();
 						if(parent == null) /* root node, just stay there */
 							nextTreeNode = treeNode;
 						else /* loop = go to first child of own parent */
-							nextTreeNode = (DiagramModelTreeNode)parent.getFirstChild();
+							nextTreeNode = (DiagramTreeNode)parent.getFirstChild();
 						loop = SoundEvent.LIST_BOTTOM_REACHED;
 					}
 					setSelectionPath(new TreePath(nextTreeNode.getPath()));
@@ -376,15 +442,15 @@
 		   getActionMap().put("up", new AbstractAction(){
 				@Override
 				public void actionPerformed(ActionEvent evt) {
-					DiagramModelTreeNode treeNode = (DiagramModelTreeNode)getLastSelectedPathComponent();
-					DiagramModelTreeNode previousTreeNode = treeNode.getPreviousSibling();
+					DiagramTreeNode treeNode = (DiagramTreeNode)getLastSelectedPathComponent();
+					DiagramTreeNode previousTreeNode = treeNode.getPreviousSibling();
 					SoundEvent loop = null;
 					if(previousTreeNode == null){
-						DiagramModelTreeNode parent = treeNode.getParent();
+						DiagramTreeNode parent = treeNode.getParent();
 						if(parent == null) /* root node */
 							previousTreeNode = treeNode;
 						else 
-							previousTreeNode = (DiagramModelTreeNode)parent.getLastChild();
+							previousTreeNode = (DiagramTreeNode)parent.getLastChild();
 						loop = SoundEvent.LIST_TOP_REACHED;
 					}
 					setSelectionPath(new TreePath(previousTreeNode.getPath()));
@@ -404,14 +470,14 @@
 			   @Override
 			   public void actionPerformed(ActionEvent evt) {
 				   TreePath path = getSelectionPath();
-				   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent();
+				   DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();
 				   if(treeNode.isLeaf()){
 					   notifyBorderReached(treeNode);
 					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move right","border reached");
 				   }
 				   else{
 					   expandPath(path);
-					   setSelectionPath(new TreePath(((DiagramModelTreeNode)treeNode.getFirstChild()).getPath()));
+					   setSelectionPath(new TreePath(((DiagramTreeNode)treeNode.getFirstChild()).getPath()));
 					   final String currentPathSpeech = currentPathSpeech();
 					   SoundFactory.getInstance().play(SoundEvent.TREE_NODE_EXPAND,new PlayerListener(){
 						@Override
@@ -419,7 +485,7 @@
 							NarratorFactory.getInstance().speak(currentPathSpeech);
 						}
 					   });
-					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move right",((DiagramModelTreeNode)treeNode.getFirstChild()).toString());
+					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move right",((DiagramTreeNode)treeNode.getFirstChild()).toString());
 				   }
 			   }
 		   });
@@ -429,14 +495,14 @@
 			   @Override
 			   public void actionPerformed(ActionEvent evt) {
 				   TreePath path = getSelectionPath();
-				   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent();
-				   DiagramModelTreeNode parent = treeNode.getParent();
+				   DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();
+				   DiagramTreeNode parent = treeNode.getParent();
 				   if(parent == null){/* root node */
 					   notifyBorderReached(treeNode);
 					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move left","border reached");
 				   }
 				   else{
-					   TreePath newPath = new TreePath(((DiagramModelTreeNode)parent).getPath());
+					   TreePath newPath = new TreePath(((DiagramTreeNode)parent).getPath());
 					   setSelectionPath(newPath);
 					   collapsePath(newPath);
 					   final String currentPathSpeech = currentPathSpeech(); 
@@ -446,7 +512,7 @@
 								NarratorFactory.getInstance().speak(currentPathSpeech);
 							}
 					   });
-					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move left",((DiagramModelTreeNode)parent).toString());
+					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move left",((DiagramTreeNode)parent).toString());
 				   }
 			   }
 		   });
@@ -474,7 +540,7 @@
 				   *  Narrator.getInstance().speak(builder.toString(), null);
 				   */  
 				   TreePath path = getSelectionPath();
-				   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent();
+				   DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();
 				   NarratorFactory.getInstance().speak(treeNode.detailedSpokenText());
 				   InteractionLog.log(INTERACTIONLOG_SOURCE,"detailed info requested","");
 			   }
@@ -486,14 +552,11 @@
 			   public void actionPerformed(ActionEvent evt) {
 				   if(getSelectionPath().getLastPathComponent() instanceof Node){
 					   Node node = (Node)getSelectionPath().getLastPathComponent();
-					   
-					   
 					   if(selectedNodes.contains(node)){
 						   unselectNode(node);
-						   diagram.getModelUpdater().yieldLock(node, Lock.MUST_EXIST);
-					   }
-					   else{
-						   if(!diagram.getModelUpdater().getLock(node, Lock.MUST_EXIST)){
+						   diagram.getModelUpdater().yieldLock(node, Lock.MUST_EXIST,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION,node.getId(),node.getName()));
+					   }else{
+						   if(!diagram.getModelUpdater().getLock(node, Lock.MUST_EXIST,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SELECT_NODE_FOR_EDGE_CREATION,node.getId(),node.getName()))){
 							   InteractionLog.log(INTERACTIONLOG_SOURCE,"Could not get lock on node fro edge creation selection",DiagramElement.toLogString(node));
 							   SpeechOptionPane.showMessageDialog(
 									   SpeechOptionPane.getFrameForComponent(DiagramTree.this), 
@@ -521,7 +584,7 @@
 		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0),"none");
 	   }
 	
-	private static InputStream getTreeNodeSound(DiagramModelTreeNode node){
+	private static InputStream getTreeNodeSound(DiagramTreeNode node){
 		InputStream sound = null;
 		TreeNode[] newPath = node.getPath();
 		if(!node.isRoot()){
@@ -550,7 +613,7 @@
 		scrollPathToVisible(path);
 	}
 	
-	private void notifyBorderReached(DiagramModelTreeNode n) {
+	private void notifyBorderReached(DiagramTreeNode n) {
 		SoundFactory.getInstance().play(SoundEvent.ERROR);
 	}
 	
@@ -582,7 +645,30 @@
 	private static final char SELECTED_NODE_MARK_BEGIN = '<';
 	private static final char SELECTED_NODE_MARK_END = '>';
 	private static final String INTERACTIONLOG_SOURCE = "TREE";
-	static enum JumpTo {REFERENCE, ROOT, TYPE, SELECTED_TYPE, BOOKMARK}
+	/**
+	 * A list of possible destination for a jump (a change of the selected path without 
+	 * using the navigation arrow keys)   
+	 */
+	public static enum JumpTo {
+		/**
+		 * if the current selection is a edge/node reference tree node, the jump destination 
+		 * is the referee tree node (see {@link uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode} and 
+		 * {@link uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode }) 
+		 */
+		REFERENCE,
+		/**
+		 * the destination is the root of the diagram
+		 */
+		ROOT,
+		/**
+		 * the destination will be a node or edge type selected 
+		 * (via a selection dialog) by the user
+		 */
+		SELECTED_TYPE, 
+		 /**	
+		  * the destination will be a bookmark selected (via a selection dialog) by the user 
+		  */
+		BOOKMARK}
 	
 	/* the methods of the TreeModelHandler are overwritten in order to provide a consistent way 
 	 * of updating the tree selection upon tree change. Bear in mind that the tree can possibly be changed
@@ -614,14 +700,14 @@
 				/* go along the path from the selected node to the root looking for a node *
 				 * attached to the tree or with sibling nodes attached to the tree         */
 				for(int i=pathArray.length-1;i>=0;i--){
-					DiagramModelTreeNode onPathTreeNode = (DiagramModelTreeNode)pathArray[i];
+					DiagramTreeNode onPathTreeNode = (DiagramTreeNode)pathArray[i];
 					if(onPathTreeNode.isNodeRelated(root)){// if can reach the root from here a.k.a. the node is still part of the tree 
 						super.treeStructureChanged(e);
 						setSelectionPath(new TreePath(onPathTreeNode.getPath()));
 						break;
 					}else{
 						/* check sibling nodes*/
-						DefaultMutableTreeNode parent = (DiagramModelTreeNode)pathArray[i-1];
+						DefaultMutableTreeNode parent = (DiagramTreeNode)pathArray[i-1];
 						if(parent.isNodeRelated(root) && parent.getChildCount() > 0){
 							super.treeStructureChanged(e);
 							setSelectionPath(new TreePath(((DefaultMutableTreeNode)parent.getLastChild()).getPath()));
@@ -645,7 +731,7 @@
 		public void treeNodesRemoved(final TreeModelEvent e){
 			/* check first if what we're removing is in the selecton path */
 			TreePath path = e.getTreePath();
-			DiagramModelTreeNode removedTreeNode = (DiagramModelTreeNode)e.getChildren()[0];
+			DiagramTreeNode removedTreeNode = (DiagramTreeNode)e.getChildren()[0];
 			boolean isInSelectionPath = false;
 			for(Object t : getSelectionPath().getPath()){
 				if(removedTreeNode == t){
@@ -653,7 +739,7 @@
 					break;
 				}
 			}
-			DiagramModelTreeNode parentTreeNode = (DiagramModelTreeNode)path.getLastPathComponent(); 
+			DiagramTreeNode parentTreeNode = (DiagramTreeNode)path.getLastPathComponent(); 
 			/* update the selection only if the tree node involved is in the selection path *
 			 * this always holds true for tree nodes deleted from the tree                  */
 			if(isInSelectionPath){
@@ -670,7 +756,7 @@
 					 * and the node has siblings than the new first sibling will be selected              */
 					int limitForParentDeletion = (parentTreeNode instanceof Edge) ? 1 : 0; // an edge with one node is to be deleted
 					if(parentTreeNode.getChildCount() > limitForParentDeletion){
-						setSelectionPath(new TreePath(((DiagramModelTreeNode)parentTreeNode.getChildAt(
+						setSelectionPath(new TreePath(((DiagramTreeNode)parentTreeNode.getChildAt(
 								/* select the n-th sibling node (see algorithm description above or the highest index sibling node */
 								Math.min(e.getChildIndices()[0],parentTreeNode.getChildCount()-1)
 						)).getPath()));
@@ -678,7 +764,7 @@
 						/* the deleted node had no siblings, thus select the node checking from the parent up in the path to the first still existing node */
 						Object[] pathArray = path.getPath();
 						for(int i=path.getPathCount()-1;i>=0;i--){
-							DiagramModelTreeNode itr = (DiagramModelTreeNode)pathArray[i];
+							DiagramTreeNode itr = (DiagramTreeNode)pathArray[i];
 							if(itr.getPath()[0] == getModel().getRoot()){
 								TreePath newPath = new TreePath(itr.getPath()); 
 								setSelectionPath(newPath);
@@ -692,7 +778,7 @@
 				super.treeNodesRemoved(e);
 
 			/* if the node was selected for edge creation, then remove it from the list */
-			DiagramModelTreeNode removedNode = (DiagramModelTreeNode)e.getChildren()[0];
+			DiagramTreeNode removedNode = (DiagramTreeNode)e.getChildren()[0];
 			selectedNodes.remove(removedNode);
 		}		
 	}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Direction.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Direction.java	Wed Apr 25 17:09:09 2012 +0100
@@ -56,6 +56,12 @@
          q.getY() - p.getY());
    }
    
+   /**
+    * Checks whether the direction passed as argument is parallel to this direction.
+    * 
+    * @param d the direction to check against 
+    * @return {@code true} if this direction and {@code d} are parallel to each other, false otherwise
+    */
    public boolean isParallel(Direction d){
 	   if(equals(d.x,d.y,DELTA)||turn(180).equals(d.x,d.y,DELTA))
 		   return true;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java	Wed Apr 25 17:09:09 2012 +0100
@@ -1,6 +1,6 @@
 /*  
  CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-	  
+
  Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
  Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
 
@@ -16,7 +16,7 @@
 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+ */
 
 package uk.ac.qmul.eecs.ccmi.gui;
 
@@ -57,6 +57,18 @@
 @SuppressWarnings("serial")
 public abstract class Edge extends DiagramEdge implements GraphElement{
 
+	/**
+	 * Creates a new Edge 
+	 * 
+	 * @param type the type of the edge. The type is just a string that the user assign to the edge.  
+	 * All the edges created via clonation from this edge will have the same type.  
+	 * @param availableEndDescriptions all the possible end description ends of this edge can be
+	 * associated to. An end description is a text associated to a possible arrow head of an edge end. 
+	 * It's used to give awareness of arrow heads via speech.     
+	 * @param minAttachedNodes the minimum number of nodes that can be attached to this edge 
+	 * @param maxAttachedNodes the minimum number of nodes that can be attached to this edge 
+	 * @param style the style of the edge: whether it's solid, dotted or dashed 
+	 */
 	public Edge(String type, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes,LineStyle style){
 		super(type,availableEndDescriptions);
 		this.minAttachedNodes = minAttachedNodes;
@@ -64,47 +76,175 @@
 		this.style = style;
 		nodes = new ArrayList<Node>();
 	}
-   
+
+	/* --- Methods inherited from DiagramEdge --- */
 	@Override
-   public Node getNodeAt(int index){
-	   return nodes.get(index);
-   }
-   
-   @Override
-   public int getNodesNum(){
-	   return nodes.size();
-   }
-   
-   public boolean removeNode(DiagramNode diagramNode){
-	   Node n = (Node)diagramNode;
-	   if(nodes.size() == 2)
-		   throw new RuntimeException("Cannot remove a node from a two ends edge");
-	   else{
-		   for(InnerPoint p : points)
-			   if(p.hasNeighbour(n)){
-				   p.neighbours.remove(n);
-				   if(p.neighbours.size() == 1)
-					   removePoint(p);
-				   break;
-			   } 
-		   return nodes.remove(n);
-	   }
-   }
+	public Node getNodeAt(int index){
+		return nodes.get(index);
+	}
 
-   /**
-    * Look for the node attached to this edge which lays at the minimum distance 
-    * from the point passed as argument. The distance cannot be lower than the    
-    * value passed as argument. 
-    * 
-    * @param aPoint the point the distance is measured from
-    * @param distanceLimit the limit from the distance between the nodes and the point
-    * @return the closest node or null if the node lays at an higher distance than distanceLimit
-    */
-   public Node getClosestNode(Point2D aPoint, double distanceLimit){
-	   Node closestNode = null;
-	   double minDist = distanceLimit;
-	   
-	   if(points.isEmpty()){
+	@Override
+	public int getNodesNum(){
+		return nodes.size();
+	}
+
+	@Override
+	public boolean removeNode(DiagramNode diagramNode){
+		Node n = (Node)diagramNode;
+		if(nodes.size() == 2)
+			throw new RuntimeException("Cannot remove a node from a two ends edge");
+		else{
+			for(InnerPoint p : points)
+				if(p.hasNeighbour(n)){
+					p.neighbours.remove(n);
+					if(p.neighbours.size() == 1)
+						removePoint(p);
+					break;
+				} 
+			return nodes.remove(n);
+		}
+	}
+
+	@Override
+	public void connect(List<DiagramNode> nodes) throws ConnectNodesException{
+		assert(getNodesNum() == 0);
+		/* this is to eliminate duplicates */
+		LinkedHashSet<Node> nodeSet = new LinkedHashSet<Node>();
+		for(DiagramNode n : nodes)
+			nodeSet.add((Node)n);
+	
+		/* checks on connection consistency */
+		if((nodeSet == null)||(nodeSet.size() < minAttachedNodes))
+			throw new ConnectNodesException("You must select at least "+ minAttachedNodes + "nodes");
+		if((nodeSet.size() > maxAttachedNodes))
+			throw new ConnectNodesException("You must select at most " + maxAttachedNodes +" nodes");
+	
+		points = new ArrayList<InnerPoint>();
+		if(nodeSet.size() > 2){
+			/* there are more than three nodes. compute the central inner point         *
+			 * which will connect all the nodes, as the middle points of the edge bound */
+			Rectangle2D bounds = new Rectangle2D.Double();
+			for(Node n : nodeSet){
+				bounds.add(n.getBounds());
+			}
+			InnerPoint p = new InnerPoint();
+			p.translate(new Point2D.Double(0,0), bounds.getCenterX(), bounds.getCenterY(),DiagramEventSource.NONE);
+			p.neighbours.addAll(nodeSet);
+			points.add(p);
+		}
+		this.nodes.addAll(nodeSet);
+	
+		if(!points.isEmpty())// points is empty when the edge has two nodes only 
+			masterInnerPoint = points.get(0);
+	}
+
+	@Override
+	public abstract void draw(Graphics2D g2);
+
+	@Override
+	public void translate(Point2D p, double dx, double dy, Object source){
+		for(InnerPoint ip : points)
+			ip.translate(p, dx, dy,source);
+	}
+
+	/**
+	 * To be called before {@code bend}, determines from {@code downPoint} whether 
+	 * a line is going to be break into two lines (with the creation of a new inner point) 
+	 * or if the bending is determined by an already existing inner point being translated 
+	 */
+	@Override
+	public void startMove(Point2D downPoint,Object source){
+		this.downPoint = downPoint;
+		newInnerPoint = null;
+		for(InnerPoint itrPoint : points)
+			if(itrPoint.contains(downPoint)){
+				/* clicked on an already existing EdgePoint */
+				newInnerPoint = itrPoint;
+				newPointCreated = false;
+			}
+		if(newInnerPoint == null){
+			/* no point under the click, create a new one */
+			newInnerPoint = new InnerPoint();
+			newInnerPoint.translate(downPoint, downPoint.getX() - newInnerPoint.getBounds().getCenterX(),
+					downPoint.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE);
+			newPointCreated = true;
+			/* this methods checks for segments of the edge which are aligned and makes a unique edge out of them */
+		}
+	}
+	
+	/**
+	 * If this edge is made out of several lines and two or more of them becomes
+	 * aligned then they are blended in one single line and the inner point that was
+	 * at the joint is removed. 
+	 * 
+	 * @param source the source of the {@code stopMove} action
+	 */
+	@Override
+	public void stopMove(Object source){
+		for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){
+			InnerPoint ePoint = pItr.next();
+			if(ePoint.neighbours.size() > 2)
+				continue;
+			Rectangle2D startBounds = ePoint.getBounds();
+			Rectangle2D endBounds = ePoint.neighbours.get(0).getBounds();
+			Direction d1 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
+			endBounds = ePoint.neighbours.get(1).getBounds();
+			Direction d2 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
+			if(d1.isParallel(d2)){
+				InnerPoint p = null;
+				GraphElement q = null;
+				if(ePoint.neighbours.get(0) instanceof InnerPoint){
+					p = (InnerPoint)ePoint.neighbours.get(0);
+					q = ePoint.neighbours.get(1);
+					p.neighbours.add(q);
+					p.neighbours.remove(ePoint);
+				}
+				if(ePoint.neighbours.get(1) instanceof InnerPoint){
+					p = (InnerPoint)ePoint.neighbours.get(1);
+					q = ePoint.neighbours.get(0);
+					p.neighbours.add(q);
+					p.neighbours.remove(ePoint);
+				}
+				pItr.remove();
+			}
+		}
+		notifyChange(new ElementChangedEvent(this,this,"stop_move",source));
+	}
+
+	@Override
+	public abstract Rectangle2D getBounds();
+
+	@Override
+	public Point2D getConnectionPoint(Direction d){return null;}
+
+	@Override
+	public boolean contains(Point2D aPoint){
+		if(points.isEmpty()){ 
+			return  fatStrokeContains (getSegment(nodes.get(0), nodes.get(1)), aPoint);
+		}
+		for(InnerPoint p : points){
+			for(GraphElement ge : p.neighbours){
+				if(fatStrokeContains(getSegment(p,ge),aPoint))
+					return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Look for the node attached to this edge which lays at the minimum distance 
+	 * from the point passed as argument. The distance cannot be lower than the    
+	 * value passed as argument. 
+	 * 
+	 * @param aPoint the point the distance is measured from
+	 * @param distanceLimit the limit from the distance between the nodes and the point
+	 * @return the closest node or null if the node lays at an higher distance than distanceLimit
+	 */
+	public Node getClosestNode(Point2D aPoint, double distanceLimit){
+		Node closestNode = null;
+		double minDist = distanceLimit;
+
+		if(points.isEmpty()){
 			Line2D line = getSegment(nodes.get(0),nodes.get(1)); 
 			if(line.getP1().distance(aPoint) < minDist){
 				minDist = line.getP1().distance(aPoint);
@@ -129,272 +269,200 @@
 			}
 			return closestNode;
 		}
-   }
-   
-   //------------------------
-   private void removePoint(InnerPoint p){
-	   /* we assume at this moment p has one neighbours only */
-	   InnerPoint neighbour = (InnerPoint)p.neighbours.get(0);
-	   points.remove(p);
-	   neighbour.neighbours.remove(p);
-	   if(neighbour.neighbours.size() == 1)
-		   removePoint(neighbour);
-   }
-   
-   /**
-   Draw the edge.
-   @param g2 the graphics context
-   */
-   public abstract void draw(Graphics2D g2);
+	}
 
-   /**
-	Tests whether the edge contains a point.
-	@param aPoint the point to test
-	@return true if this edge contains aPoint
-   */
-   public boolean contains(Point2D aPoint){
-	   if(points.isEmpty()){ 
-		   return  fatStrokeContains (getSegment(nodes.get(0), nodes.get(1)), aPoint);
-	   }
-	   for(InnerPoint p : points){
-		   for(GraphElement ge : p.neighbours){
-			   if(fatStrokeContains(getSegment(p,ge),aPoint))
-				   return true;
-		   }
-	   }
-	   return false;
-   }
-   
-   private boolean fatStrokeContains(Shape s, Point2D p){
-	   BasicStroke fatStroke = new BasicStroke((float) (2 * MAX_DIST));
-	   Shape fatPath = fatStroke.createStrokedShape(s);
-	   return fatPath.contains(p);
-   }
-   
-   protected Line2D.Double getSegment(GraphElement start, GraphElement end){
-	   Rectangle2D startBounds = start.getBounds();
-	   Rectangle2D endBounds = end.getBounds();
-	   Direction d = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
-	   return new Line2D.Double(start.getConnectionPoint(d), end.getConnectionPoint(d.turn(180)));
-   }
-   
-   public List<Point2D> getConnectionPoints(){
-	   List<Point2D> list = new LinkedList<Point2D>();
-	   if(points.isEmpty()){
-		   Line2D line = getSegment(nodes.get(0),nodes.get(1)); 
-		   list.add(line.getP1());
-		   list.add(line.getP2());
-	   }else{
-		   for(InnerPoint p : points){
-			   for(GraphElement ge : p.neighbours)
-				   if(ge instanceof Node){
-					   Direction d = new Direction(p.getBounds().getCenterX() - ge.getBounds().getCenterX(),p.getBounds().getCenterY() - ge.getBounds().getCenterY());
-					   list.add(((Node)ge).getConnectionPoint(d));
-				   }
-		   }
-	   }
-	   return list;
-   }
-   
-   public abstract int getStipplePattern();
-	
-   /**
-	Connect this edge to the nodes.
-	@param aStart the starting node
-	@param anEnd the ending node
-   */
-   public void connect(List<DiagramNode> nodes) throws ConnectNodesException{
-	   assert(getNodesNum() == 0);
-	   /* this is to eliminate duplicates */
-	   LinkedHashSet<Node> nodeSet = new LinkedHashSet<Node>();
-	   for(DiagramNode n : nodes)
-		   nodeSet.add((Node)n);
+	private void removePoint(InnerPoint p){
+		/* we assume at this moment p has one neighbour only */
+		InnerPoint neighbour = (InnerPoint)p.neighbours.get(0);
+		points.remove(p);
+		neighbour.neighbours.remove(p);
+		if(neighbour.neighbours.size() == 1)
+			removePoint(neighbour);
+	}
 
-	   /* checks on connection consistency */
-	   if((nodeSet == null)||(nodeSet.size() < minAttachedNodes))
-		   throw new ConnectNodesException("You must select at least "+ minAttachedNodes + "nodes");
-	   if((nodeSet.size() > maxAttachedNodes))
-		   throw new ConnectNodesException("You must select at most " + maxAttachedNodes +" nodes");
-	   
-	   points = new ArrayList<InnerPoint>();
-	   if(nodeSet.size() > 2){
-		   /* there are more than three nodes. compute the central inner point         *
-		    * which will connect all the nodes, as the middle points of the edge bound */
-		   Rectangle2D bounds = new Rectangle2D.Double();
-		   for(Node n : nodeSet){
-			   bounds.add(n.getBounds());
-		   }
-		   InnerPoint p = new InnerPoint();
-		   p.translate(new Point2D.Double(0,0), bounds.getCenterX(), bounds.getCenterY());
-		   p.neighbours.addAll(nodeSet);
-		   points.add(p);
-	   }
-	   this.nodes.addAll(nodeSet);
-	   
-	   if(!points.isEmpty())
-			masterInnerPoint = points.get(0);
-   }
-   
-   public void bend(Point2D p) {
-	   boolean found = false;
-	   if(points.isEmpty()){
-		   newInnerPoint.neighbours.addAll(nodes); 
-		   points.add(newInnerPoint);
-		   newPointCreated = false;
-	   }else if(newPointCreated){
-		   /* find the segment where the new point lays */
-		   for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext() && !found; ){
-			   InnerPoint ePoint = pItr.next();
-			   for(ListIterator<GraphElement> geItr = ePoint.neighbours.listIterator(); geItr.hasNext() && !found;){
-			       /* find the neighbour of the current edge point whose line the new point lays on */
-				   GraphElement ge = geItr.next();
-				   if(fatStrokeContains(getSegment(ePoint, ge),downPoint)){
-					   if(ge instanceof InnerPoint ){
-						   /* remove current edge point from the neighbour's neighbours */
-						   ((InnerPoint)ge).neighbours.remove(ePoint);
-						   ((InnerPoint)ge).neighbours.add(newInnerPoint);
-					   }
-					   /*remove old neighbour from edgePoint neighbours */
-					   geItr.remove();
-					   newInnerPoint.neighbours.add(ePoint);
-					   newInnerPoint.neighbours.add(ge);
-					   /* add the new node to the list of EdgeNodes of this edge */
-					   pItr.add(newInnerPoint);
-					   geItr.add(newInnerPoint);
-					   found = true;
-				   }
-			   }
-		   }
-		   newPointCreated = false;
-	   }
-	   newInnerPoint.translate(p, p.getX() - newInnerPoint.getBounds().getCenterX(),
-			            p.getY() - newInnerPoint.getBounds().getCenterY());
-	   notifyChange(new ElementChangedEvent(this,this,"bend"));
-   }
-   
-   /*
-    * this methods checks for segments of the edge which are aligned and makes a unique edge out of them
-    */
-   public void stopMove(){
-	   for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){
-		   InnerPoint ePoint = pItr.next();
-		   if(ePoint.neighbours.size() > 2)
-			   continue;
-		   Rectangle2D startBounds = ePoint.getBounds();
-		   Rectangle2D endBounds = ePoint.neighbours.get(0).getBounds();
-		   Direction d1 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
-		   endBounds = ePoint.neighbours.get(1).getBounds();
-		   Direction d2 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
-		   if(d1.isParallel(d2)){
-			   InnerPoint p = null;
-			   GraphElement q = null;
-			   if(ePoint.neighbours.get(0) instanceof InnerPoint){
-				    p = (InnerPoint)ePoint.neighbours.get(0);
-			   		q = ePoint.neighbours.get(1);
-			   		p.neighbours.add(q);
-			   		p.neighbours.remove(ePoint);
-			   }
-			   if(ePoint.neighbours.get(1) instanceof InnerPoint){
-				    p = (InnerPoint)ePoint.neighbours.get(1);
-			   		q = ePoint.neighbours.get(0);
-			   		p.neighbours.add(q);
-			   		p.neighbours.remove(ePoint);
-			   }
-			   pItr.remove();
-		   }
-	   }
-	   notifyChange(new ElementChangedEvent(this,this,"stop_move"));
-   }
-   
-   public void startMove(Point2D downPoint){
-	   this.downPoint = downPoint;
-	   newInnerPoint = null;
-	   for(InnerPoint itrPoint : points)
-		   if(itrPoint.contains(downPoint)){
-			   /* clicked on an already existing EdgePoint */
-			   newInnerPoint = itrPoint;
-			   newPointCreated = false;
-		   }
-	   if(newInnerPoint == null){
-		   /* no point under the click, create a new one */
-		   newInnerPoint = new InnerPoint();
-	   	   newInnerPoint.translate(downPoint, downPoint.getX() - newInnerPoint.getBounds().getCenterX(),
-	            downPoint.getY() - newInnerPoint.getBounds().getCenterY());
-	   	   newPointCreated = true;
-	   }
-   }
-   
-   @Override
-   public void translate(Point2D p, double dx, double dy){
-	   for(InnerPoint ip : points)
-		   ip.translate(p, dx, dy);
-   }
-  
-   public Line2D getNameLine(){
+	/* checks if a point belongs to a shape with a margin of MAX_DIST*/
+	private boolean fatStrokeContains(Shape s, Point2D p){
+		BasicStroke fatStroke = new BasicStroke((float) (2 * MAX_DIST));
+		Shape fatPath = fatStroke.createStrokedShape(s);
+		return fatPath.contains(p);
+	}
+
+	/**
+	 * Returns a line connecting the centre of the graph elements passed as argument.
+	 * 
+	 * @param start the first graph element 
+	 * @param end the second graph element 
+	 * 
+	 * @return a line connecting {@code start} and {@code end}
+	 */
+	protected Line2D.Double getSegment(GraphElement start, GraphElement end){
+		Rectangle2D startBounds = start.getBounds();
+		Rectangle2D endBounds = end.getBounds();
+		Direction d = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
+		return new Line2D.Double(start.getConnectionPoint(d), end.getConnectionPoint(d.turn(180)));
+	}
+
+	/**
+	 * Returns a list of the points where this edge and the nodes it connects come to a contact
+	 * 
+	 * @return a list of points
+	 */
+	public List<Point2D> getConnectionPoints(){
+		List<Point2D> list = new LinkedList<Point2D>();
+		if(points.isEmpty()){
+			Line2D line = getSegment(nodes.get(0),nodes.get(1)); 
+			list.add(line.getP1());
+			list.add(line.getP2());
+		}else{
+			for(InnerPoint p : points){
+				for(GraphElement ge : p.neighbours)
+					if(ge instanceof Node){
+						Direction d = new Direction(p.getBounds().getCenterX() - ge.getBounds().getCenterX(),p.getBounds().getCenterY() - ge.getBounds().getCenterY());
+						list.add(((Node)ge).getConnectionPoint(d));
+					}
+			}
+		}
+		return list;
+	}
+
+	/**
+	 * Gets the stipple pattern of this edge line style 
+	 * 
+	 * @see LineStyle#getStipplePattern() 
+	 * 
+	 * @return an int representing the stipple pattern of this edge
+	 */
+	public int getStipplePattern(){
+		return getStyle().getStipplePattern();
+	}
+
+	/**
+	 * Bends one of the lines forming this edge. 
+	 * 
+	 * When an line is bent, if the location where the bending happens is a line then a new 
+	 * inner point is created and the line is broken into two sub lines. If the location is an 
+	 * already existing inner point, then the point is translated.
+	 * 
+	 * @param p
+	 * @param source
+	 */
+	public void bend(Point2D p,Object source) {
+		boolean found = false;
+		if(points.isEmpty()){
+			newInnerPoint.neighbours.addAll(nodes); 
+			points.add(newInnerPoint);
+			newPointCreated = false;
+		}else if(newPointCreated){
+			/* find the segment where the new point lays */
+			for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext() && !found; ){
+				InnerPoint ePoint = pItr.next();
+				for(ListIterator<GraphElement> geItr = ePoint.neighbours.listIterator(); geItr.hasNext() && !found;){
+					/* find the neighbour of the current edge point whose line the new point lays on */
+					GraphElement ge = geItr.next();
+					if(fatStrokeContains(getSegment(ePoint, ge),downPoint)){
+						if(ge instanceof InnerPoint ){
+							/* remove current edge point from the neighbour's neighbours */
+							((InnerPoint)ge).neighbours.remove(ePoint);
+							((InnerPoint)ge).neighbours.add(newInnerPoint);
+						}
+						/*remove old neighbour from edgePoint neighbours */
+						geItr.remove();
+						newInnerPoint.neighbours.add(ePoint);
+						newInnerPoint.neighbours.add(ge);
+						/* add the new node to the list of EdgeNodes of this edge */
+						pItr.add(newInnerPoint);
+						geItr.add(newInnerPoint);
+						found = true;
+					}
+				}
+			}
+			newPointCreated = false;
+		}
+		newInnerPoint.translate(p, p.getX() - newInnerPoint.getBounds().getCenterX(),
+				p.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE);
+		notifyChange(new ElementChangedEvent(this,this,"bend",source));
+	}
+
+	/**
+	 * Returns the line where the edge name will be painted. If the edge is only made out
+	 * of a straight line then this will be returned. 
+	 * 
+	 * If the edge has been broken into several segments (by bending it) then the central 
+	 * line is returned. If the edge connects more than two nodes then a line (not necessarily
+	 * matching the edge) that has the central point in its centre is returned. Note that a 
+	 * edge connecting more than two nodes is painted as a central point connected to all the nodes.  
+	 * 
+	 * @return the line where the name will be painted
+	 */
+	public Line2D getNameLine(){
 		if(points.isEmpty()){ // straight line 
 			return getSegment(nodes.get(0),nodes.get(1));
 		}else{
 			if(masterInnerPoint != null){/* multiended edge  */
-				 Rectangle2D bounds = masterInnerPoint.getBounds();
-				 Point2D p = new Point2D.Double(bounds.getCenterX() - 1,bounds.getCenterY());
-				 Point2D q = new Point2D.Double(bounds.getCenterX() + 1,bounds.getCenterY());
-				 return new Line2D.Double(p, q);
+				Rectangle2D bounds = masterInnerPoint.getBounds();
+				Point2D p = new Point2D.Double(bounds.getCenterX() - 1,bounds.getCenterY());
+				Point2D q = new Point2D.Double(bounds.getCenterX() + 1,bounds.getCenterY());
+				return new Line2D.Double(p, q);
 			}else{
 				GraphElement ge1 = nodes.get(0);
-				 GraphElement ge2 = nodes.get(1);
-				 InnerPoint c1 = null;
-				 InnerPoint c2 = null;
-					 
-				 for(InnerPoint innp : points){
-					 if(innp.getNeighbours().contains(ge1)){
-						 c1 = innp;
-					 }
-					 if(innp.getNeighbours().contains(ge2)){
-						 c2 = innp;
-					 }
-				 }
-				
-				 /* we only have two nodes but the edge has been bended */
-				 while((c1 != c2)&&(!c2.getNeighbours().contains(c1))){
-					 if(c1.getNeighbours().get(0) == ge1){
-						 ge1 = c1;
-						 c1 = (InnerPoint)c1.getNeighbours().get(1);
-					 }
-					 else{
-						 ge1 = c1;
-						 c1 = (InnerPoint)c1.getNeighbours().get(0);
-					 }
-					 if(c2.getNeighbours().get(0) == ge2){
-						 ge2 = c2;
-						 c2 = (InnerPoint)c2.getNeighbours().get(1);
-					 }
-					 else{
-						 ge2 = c2;
-						 c2 = (InnerPoint)c2.getNeighbours().get(0);
-					 }
-				 }
-				 
-				 Point2D p = new Point2D.Double();
-				 Point2D q = new Point2D.Double();
-				 if(c1 == c2){
-					 Rectangle2D bounds = c1.getBounds();
-					 p.setLocation( bounds.getCenterX() - 1,bounds.getCenterY());
-					 q.setLocation(  bounds.getCenterX() + 1,bounds.getCenterY());
-				 }else{
-					 Rectangle2D bounds = c1.getBounds();
-					 p.setLocation( bounds.getCenterX(),bounds.getCenterY());
-					 bounds = c2.getBounds();
-					 q.setLocation(bounds.getCenterX(),bounds.getCenterY());
-					 
-				 }
-				 return new Line2D.Double(p,q);
+				GraphElement ge2 = nodes.get(1);
+				InnerPoint c1 = null;
+				InnerPoint c2 = null;
+
+				for(InnerPoint innp : points){
+					if(innp.getNeighbours().contains(ge1)){
+						c1 = innp;
+					}
+					if(innp.getNeighbours().contains(ge2)){
+						c2 = innp;
+					}
+				}
+
+				/* we only have two nodes but the edge has been bended */
+				while((c1 != c2)&&(!c2.getNeighbours().contains(c1))){
+					if(c1.getNeighbours().get(0) == ge1){
+						ge1 = c1;
+						c1 = (InnerPoint)c1.getNeighbours().get(1);
+					}
+					else{
+						ge1 = c1;
+						c1 = (InnerPoint)c1.getNeighbours().get(0);
+					}
+					if(c2.getNeighbours().get(0) == ge2){
+						ge2 = c2;
+						c2 = (InnerPoint)c2.getNeighbours().get(1);
+					}
+					else{
+						ge2 = c2;
+						c2 = (InnerPoint)c2.getNeighbours().get(0);
+					}
+				}
+
+				Point2D p = new Point2D.Double();
+				Point2D q = new Point2D.Double();
+				if(c1 == c2){
+					Rectangle2D bounds = c1.getBounds();
+					p.setLocation( bounds.getCenterX() - 1,bounds.getCenterY());
+					q.setLocation(  bounds.getCenterX() + 1,bounds.getCenterY());
+				}else{
+					Rectangle2D bounds = c1.getBounds();
+					p.setLocation( bounds.getCenterX(),bounds.getCenterY());
+					bounds = c2.getBounds();
+					q.setLocation(bounds.getCenterX(),bounds.getCenterY());
+
+				}
+				return new Line2D.Double(p,q);
 			}
 		}
 	}
-	
-	@Override
-	public abstract Rectangle2D getBounds();
-	
+
+	/**
+	 * Encodes all the relevant data of this object in XML format. 
+	 * 
+	 * @param doc an XML document 
+	 * @param parent the parent XML element, where tag about this edge will be inserted 
+	 * @param nodes a list of all nodes of the diagram
+	 */
 	public void encode(Document doc, Element parent, List<Node> nodes){
 		parent.setAttribute(PersistenceManager.TYPE,getType());
 		parent.setAttribute(PersistenceManager.NAME, getName());
@@ -411,7 +479,7 @@
 				nodesTag.appendChild(nodeTag);
 			}
 		}
-		 
+
 		if(!points.isEmpty()){
 			Element pointsTag = doc.createElement(PersistenceManager.POINTS);
 			parent.appendChild(pointsTag);
@@ -419,13 +487,13 @@
 				Element pointTag = doc.createElement(PersistenceManager.POINT);
 				pointsTag.appendChild(pointTag);
 				pointTag.setAttribute(PersistenceManager.ID, String.valueOf(-(points.indexOf(point)+1)));
-				
+
 				Element positionTag = doc.createElement(PersistenceManager.POSITION);
 				pointTag.appendChild(positionTag);
 				Rectangle2D bounds = point.getBounds();
 				positionTag.setAttribute(PersistenceManager.X,String.valueOf(bounds.getX()));
 				positionTag.setAttribute(PersistenceManager.Y,String.valueOf(bounds.getY()));
-				
+
 				Element neighboursTag = doc.createElement(PersistenceManager.NEIGHBOURS);
 				pointTag.appendChild(neighboursTag);
 				StringBuilder builder = new StringBuilder();
@@ -442,42 +510,52 @@
 			}
 		}
 	}
-	
-	
-    public void decode(Document doc, Element edgeTag, Map<String,Node> nodesId) throws IOException{
-    	setName(edgeTag.getAttribute(PersistenceManager.NAME));
-    	if(getName().isEmpty())
-    		throw new IOException();
-    	try{
-    		setId(Integer.parseInt(edgeTag.getAttribute(PersistenceManager.ID)));
-    	}catch(NumberFormatException nfe){
-    		throw new IOException(nfe);
-    	}
-    	
-    	NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE);
-    	List<DiagramNode> attachedNodes = new ArrayList<DiagramNode>(nodeList.getLength());
-    	List<String> labels = new ArrayList<String>(nodeList.getLength());
-    	for(int i=0; i<nodeList.getLength();i++){
-    		String id = ((Element)nodeList.item(i)).getAttribute(PersistenceManager.ID);
-    		if(!nodesId.containsKey(id))
-    			throw new IOException();
-    		attachedNodes.add(nodesId.get(id));
-    		labels.add(((Element)nodeList.item(i)).getAttribute(PersistenceManager.LABEL));
-    	}
-    	
-    	try {
+
+
+	/**
+	 * Decodes an edge from the XML representation. 
+	 * 
+	 * @see #encode(Document, Element, List)
+	 * 
+	 * @param doc an XML document 
+	 * @param edgeTag the tag in the XML file related to this edge
+	 * @param nodesId a map linking node ids in the XML file to the {@code Node} objects they represent
+	 * @throws IOException if something goes wrong while reading the XML file
+ 	 */
+	public void decode(Document doc, Element edgeTag, Map<String,Node> nodesId) throws IOException{
+		setName(edgeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS);
+		if(getName().isEmpty())
+			throw new IOException();
+		try{
+			setId(Integer.parseInt(edgeTag.getAttribute(PersistenceManager.ID)));
+		}catch(NumberFormatException nfe){
+			throw new IOException(nfe);
+		}
+
+		NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE);
+		List<DiagramNode> attachedNodes = new ArrayList<DiagramNode>(nodeList.getLength());
+		List<String> labels = new ArrayList<String>(nodeList.getLength());
+		for(int i=0; i<nodeList.getLength();i++){
+			String id = ((Element)nodeList.item(i)).getAttribute(PersistenceManager.ID);
+			if(!nodesId.containsKey(id))
+				throw new IOException();
+			attachedNodes.add(nodesId.get(id));
+			labels.add(((Element)nodeList.item(i)).getAttribute(PersistenceManager.LABEL));
+		}
+
+		try {
 			connect(attachedNodes);
 		} catch (ConnectNodesException e) {
 			throw new IOException(e);
 		}
-		
+
 		for(int i=0; i < labels.size(); i++){
-			setEndLabel(attachedNodes.get(i), labels.get(i));
+			setEndLabel(attachedNodes.get(i), labels.get(i),DiagramEventSource.PERS);
 		}
-		
+
 		Map<String, InnerPoint> pointsId = new LinkedHashMap<String, InnerPoint>();
 		NodeList pointTagList = edgeTag.getElementsByTagName(PersistenceManager.POINT);
-		
+
 		for(int i=0; i<pointTagList.getLength(); i++){
 			InnerPoint point = new InnerPoint();
 			Element pointTag = (Element)pointTagList.item(i);
@@ -489,9 +567,9 @@
 			}catch(NumberFormatException nfe){
 				throw new IOException(nfe);
 			}
-			
+
 			pointsId.put(id, point);
-			
+
 			if(pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
 				throw new IOException();
 			Element pointPositionTag = (Element)pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
@@ -502,9 +580,9 @@
 			}catch(NumberFormatException nfe){
 				throw new IOException();
 			}
-			point.translate(new Point2D.Double(), dx, dy);
+			point.translate(new Point2D.Double(), dx, dy,DiagramEventSource.PERS);
 		}
-		
+
 		/* remove the master inner point eventually created by connect */
 		/* we're going to replace it with the one in the XML file      */
 		points.clear();
@@ -512,13 +590,13 @@
 		for(int i=0; i<pointTagList.getLength(); i++){
 			Element pointTag = (Element)pointTagList.item(i);
 			InnerPoint point = pointsId.get(pointTag.getAttribute(PersistenceManager.ID));
-			
+
 			if(pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0) == null)
 				throw new IOException();
 			Element pointNeighboursTag = (Element)pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0);
 			String pointNeighboursTagContent = pointNeighboursTag.getTextContent();
 			String[] neighboursId = pointNeighboursTagContent.split(" ");
-			
+
 			for(String neighbourId : neighboursId){
 				GraphElement ge = nodesId.get(neighbourId);
 				if(ge == null) // it ain't a node
@@ -528,56 +606,89 @@
 				point.neighbours.add(ge);
 			}
 			points.add(point);
+			if(i==0)
+				masterInnerPoint = point;
 		}
-    }
-    
-    @Override
-    public Point2D getConnectionPoint(Direction d){return null;}
-    
+	}
+
+	/**
+	 * Returns the minimum number of nodes that edge of this type can connect 
+	 * 
+	 * @return the minimum nodes for edges of this type 
+	 */
 	public int getMinAttachedNodes(){
 		return minAttachedNodes;
 	}
-	
+
+	/**
+	 * Returns the maximum number of nodes that edge of this type can connect 
+	 * 
+	 * @return the maximum nodes for edges of this type 
+	 */
 	public int getMaxAttachedNodes(){
 		return maxAttachedNodes;
 	}
-	
+
+	/**
+	 * Return the line style of this edge 
+	 * 
+	 * @return the line style of this edge 
+	 */
 	public LineStyle getStyle(){
 		return style;
 	}
-    
+
 	protected Point2D downPoint;
 	private List<Node> nodes;
+
+	/* list containing the vertex of the edge which are not nodes */
+	/**
+	 * The list of the inner points of this edge 
+	 */
+	protected List<InnerPoint> points;
+	/**
+	 * For edges connecting more than two nodes, this is the central inner point where 
+	 * all the lines from the nodes join 
+	 */
+	protected InnerPoint masterInnerPoint;
 	
-	/* list containing the vertex of the edge which are not nodes */
-	protected List<InnerPoint> points;
-	protected InnerPoint newInnerPoint;
-	protected InnerPoint masterInnerPoint;
-	protected boolean newPointCreated;
+	private boolean newPointCreated;
+	private InnerPoint newInnerPoint;
 	private int minAttachedNodes;
 	private int maxAttachedNodes;
 
 	private LineStyle style;
-	
-	
+
+
 	private static final double MAX_DIST = 3;
 	private static final Color POINT_COLOR = Color.GRAY;
-	public static final String NO_ARROW_STRING = ResourceBundle.getBundle(EditorFrame.class.getName()).getString("no_arrow_string");
+	/**
+	 * The end description for an end that has no hand description set by the user
+	 */
+	public static final String NO_ENDDESCRIPTION_STRING = ResourceBundle.getBundle(EditorFrame.class.getName()).getString("no_arrow_string");
 
-	
+
+	/**
+	 * When an edge's (straight) line is bent it breaks into two sub lines. At the point where this two 
+	 * sub lines join a square shaped point is painted. This class represent that point. Objects of this class
+	 * are graph elements and the user can click on them and translate them along the graph. 
+	 *  
+	 */
 	protected static class InnerPoint implements GraphElement{
-	
+		/**
+		 * Creates a new inner point
+		 */
 		public InnerPoint(){
 			bounds = new Rectangle2D.Double(0,0,DIM,DIM);
 			neighbours = new LinkedList<GraphElement>();
 		}
-	
+
 		@Override
-		public void startMove(Point2D p){}
-		
+		public void startMove(Point2D p, Object source){}
+
 		@Override
-		public void stopMove(){}
-	
+		public void stopMove(Object source){}
+
 		@Override
 		public void draw(Graphics2D g2){
 			Color oldColor = g2.getColor();
@@ -595,38 +706,62 @@
 		public Point2D getConnectionPoint(Direction d){
 			return new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
 		}
-		
+
 		@Override
-		public void translate(Point2D p, double dx, double dy){
+		public void translate(Point2D p, double dx, double dy, Object source){
 			bounds.setFrame(bounds.getX() + dx,
-				bounds.getY() + dy, 
-				bounds.getWidth(), 
-				bounds.getHeight());
+					bounds.getY() + dy, 
+					bounds.getWidth(), 
+					bounds.getHeight());
 		}
-		
+
 		@Override
 		public Rectangle2D getBounds(){
 			return (Rectangle2D)bounds.clone();
 		}
-		
+
+		/**
+		 * Neighbours are the graph elements (either nodes or other inner points) this inner point is 
+		 * directly connected to. Directly connected means there is a straight line from this node
+		 * and the neighbour.  
+		 * 
+		 * @return a list of neighbours of this inner node
+		 */
 		public List<GraphElement> getNeighbours(){
 			return neighbours;
 		}
-		
-		public boolean hasNeighbour(GraphElement neighbour){
-			return neighbours.contains(neighbour);
+
+		/**
+		 * Returns true if this inner node and {@code ge} are neighbours. 
+		 * 
+		 *  @see #getNeighbours()
+		 * 
+		 * @param ge the graph element to be tested
+		 * @return {@code true} if {@code ge} is a neighbour of this graph element, {@code false} otherwise 
+		 * 
+		 */
+		public boolean hasNeighbour(GraphElement ge){
+			return neighbours.contains(ge);
 		}
-		
+
 		@Override
 		public String toString(){
 			return "EdgePoint: "+bounds.getCenterX()+"-"+bounds.getCenterY();
 		}
-		
+
 		private Rectangle2D bounds;
 		private List<GraphElement> neighbours; 
 		private static final int DIM = 5;
 	}
-	
+
+	/**
+	 * A representation of this edge as a set of 2D points.
+	 * Every node the edge connects and every inner point are represented as a pair with coordinates 
+	 * of their centre. Furthermore an adjacency matrix holds the information 
+	 * about which point is connected to which is. This representation of the edge is
+	 * used in the haptic space, being more suitable for haptic devices.  	   
+	 *
+	 */
 	public static class PointRepresentation {
 		public PointRepresentation(int size){
 			xs = new double[size];
@@ -636,12 +771,36 @@
 				adjMatrix[i] = new BitSet(size); 
 			}
 		}
+		/**
+		 * An array with all the x coordinate of the edge's points (nodes and inner points)
+		 */
 		public double xs[];
+		/**
+		 * An array with all the y coordinate of the edge's points (nodes and inner points)
+		 */
 		public double ys[];
+		/**
+		 * The adjacency matrix. If the i-th bit of {@code adjMatrix[j]} is set to {@code true}
+		 * it means there is a direct line connecting the i-th point (coordinates {@code (xs[i],ys[i])} 
+		 * to the j-th point (coordinates {@code (xs[j],ys[j])}. Note that connection are represented only
+		 * once to avoid double paintings by the haptic engine. So if the i-th bit of {@code adjMatrix[j]} 
+		 * is set to {@code true}, the j-th bit of {@code adjMatrix[i]} and information redundancy is avoided. 
+		 */
 		public BitSet adjMatrix[];
-		public int nodeStart; // the index of the beginning of the nodes in the adjMatrix
+		/** 
+		 * The index of the beginning of the nodes (after inner points) in  {@code adjMatrix}.
+		 * So if the edge has three nodes and two inner points. The inner points
+		 * will be at {@code adjMatrix[0]} and {@code adjMatrix[1]} and the nodes at the following indexes. 
+		 * In this case {@code nodeStart} is equal to 2.
+		 */
+		public int nodeStart; 
 	}
-	
+
+	/**
+	 * Returns a new {@code PointRepresentation} of this edge 
+	 * 
+	 * @return a new {@code PointRepresentation} of this edge
+	 */
 	public PointRepresentation getPointRepresentation(){
 		PointRepresentation pr = new PointRepresentation(points.size()+nodes.size());
 		if(points.isEmpty()){ // two ended edge 
@@ -657,14 +816,14 @@
 			int pSize = points.size();
 			pr.nodeStart = pSize; // the first node starts after the points
 			for(int i=0; i<pSize;i++){
-				  pr.xs[i] = points.get(i).getBounds().getCenterX();
-				  pr.ys[i] = points.get(i).getBounds().getCenterY();
-				  for(GraphElement ge : points.get(i).neighbours){
-					  if(ge instanceof InnerPoint)
-						  pr.adjMatrix[i].set(points.indexOf(ge));
-					  else //Node
-						  pr.adjMatrix[i].set(pSize+nodes.indexOf(ge));
-				  }
+				pr.xs[i] = points.get(i).getBounds().getCenterX();
+				pr.ys[i] = points.get(i).getBounds().getCenterY();
+				for(GraphElement ge : points.get(i).neighbours){
+					if(ge instanceof InnerPoint)
+						pr.adjMatrix[i].set(points.indexOf(ge));
+					else //Node
+						pr.adjMatrix[i].set(pSize+nodes.indexOf(ge));
+				}
 			}
 			/* set the coordinates of the nodes, no adj matrix needed as the inner points are enough */
 			for(int i=0 ; i<nodes.size(); i++){
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EdgePopupMenu.java	Mon Feb 06 12:54:06 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-/*  
- CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-  
- Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-package uk.ac.qmul.eecs.ccmi.gui;
-
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.text.MessageFormat;
-import java.util.ResourceBundle;
-
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPopupMenu;
-
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
-
-/**
- * A pop up menu displaying the possible operations to perform on an edge on from a visual representation
- * of a diagram.
- *
- */
-@SuppressWarnings("serial")
-public class EdgePopupMenu extends JPopupMenu {
-	
-	/**
-	 * Creates a pop up menu, showing set name, set end label and select arrow head menu items.  
-	 * @param edge the edge that will be edited 
-	 * @param node the node whose end label and arrow head that will be edited
-	 * @param parentComponent the component where the pop up will appear 
-	 * @param modelUpdater the model updater for applying changes 
-	 */
-	public EdgePopupMenu( Edge edge, Node node, Component parentComponent, DiagramModelUpdater modelUpdater){
-		this.edgeRef = edge;
-		this.nodeRef = node;
-		this.parentComponent = parentComponent;
-		this.modelUpdater = modelUpdater;
-		arrowHeads = new Object[edgeRef.getAvailableEndDescriptions().length + 1];
-		for(int i=0;i<edgeRef.getAvailableEndDescriptions().length;i++){
-			arrowHeads[i] = edgeRef.getAvailableEndDescriptions()[i].toString();
-		}
-		arrowHeads[arrowHeads.length-1] = Edge.NO_ARROW_STRING;
-		addMenuItems(false);
-	}
-	
-	/**
-	 * creates a pop up menu, showing set name menu item only 
-	 * @param edge the edge being edited 
-	 * @param parentComponent the component where the pop up will appear
-	 * @param modelUpdater the model updater for applying changes 
-	 */
-	public EdgePopupMenu( Edge edge, Component parentComponent,DiagramModelUpdater modelUpdater){
-		this.edgeRef = edge;
-		this.parentComponent = parentComponent;
-		this.modelUpdater = modelUpdater;
-		addMenuItems(true);
-	}
-	
-	private void addMenuItems(boolean showSetNameMenuItemOnly){
-		
-		JMenuItem setNameMenuItem = new JMenuItem(resources.getString("menu.set_name"));
-		setNameMenuItem.addActionListener(new ActionListener(){
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				if(!modelUpdater.getLock(edgeRef, Lock.NAME)){
-					iLog("Could not get the lock on edge  for name",DiagramElement.toLogString(edgeRef));
-					JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.name"));
-					return;
-				}
-				iLog("open rename edge dialog",DiagramElement.toLogString(edgeRef));
-				String name = JOptionPane.showInputDialog(parentComponent, MessageFormat.format(resources.getString("dialog.input.name"),edgeRef.getName()), edgeRef.getName());
-  	    	   	if(name == null)
-  	    	   		iLog("cancel rename edge dialog",DiagramElement.toLogString(edgeRef)); 
-  	    	   	else
-  	    	   		/* edge has been locked at selection time */
-  	    	   		modelUpdater.setName(edgeRef,name.trim());
-  	    	   	modelUpdater.yieldLock(edgeRef, Lock.NAME);
-			}
-		});
-		
-		add(setNameMenuItem);
-		if(!showSetNameMenuItemOnly){
-			/* Label menu item */
-			JMenuItem setLabelMenuItem = new JMenuItem(resources.getString("menu.set_label"));
-			setLabelMenuItem.addActionListener(new ActionListener(){
-				@Override
-				public void actionPerformed(ActionEvent evt) {
-					if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END)){
-						iLog("Could not get lock on edge for end label",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
-						JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end"));
-						return;
-					}
-					iLog("open edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
-					String text = JOptionPane.showInputDialog(parentComponent,resources.getString("dialog.input.label"));
-					if(text != null)
-						modelUpdater.setEndLabel(edgeRef,nodeRef,text);
-					else
-						iLog("cancel edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
-					modelUpdater.yieldLock(edgeRef, Lock.EDGE_END);
-				}
-			});
-			add(setLabelMenuItem);
-
-			if(arrowHeads.length > 1){
-				/* arrow head menu item */
-				JMenuItem selectArrowHeadMenuItem = new JMenuItem(resources.getString("menu.choose_arrow_head"));
-				selectArrowHeadMenuItem.addActionListener(new ActionListener(){
-					@Override
-					public void actionPerformed(ActionEvent e) {
-						if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END)){
-							iLog("Could not get lock on edge for arrow head",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
-							JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end"));
-							return;
-						}
-						iLog("open select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
-						String arrowHead = (String)JOptionPane.showInputDialog(
-								parentComponent, 
-								resources.getString("dialog.input.arrow"), 
-								resources.getString("dialog.input.arrow.title"), 
-								JOptionPane.PLAIN_MESSAGE, 
-								null, 
-								arrowHeads, 
-								arrowHeads);
-						if(arrowHead == null){
-							iLog("cancel select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
-							modelUpdater.yieldLock(edgeRef, Lock.EDGE_END);
-							return;
-						}
-						for(int i=0; i<edgeRef.getAvailableEndDescriptions().length;i++){
-							if(edgeRef.getAvailableEndDescriptions()[i].toString().equals(arrowHead)){
-								modelUpdater.setEndDescription(edgeRef, nodeRef, i);
-								modelUpdater.yieldLock(edgeRef, Lock.EDGE_END);
-								return;
-							}
-						}
-						/* the user selected the none menu item */
-						modelUpdater.setEndDescription(edgeRef,nodeRef, Edge.NO_END_DESCRIPTION_INDEX);
-						modelUpdater.yieldLock(edgeRef, Lock.EDGE_END);
-					}
-				});
-				add(selectArrowHeadMenuItem);
-			}
-		}
-	}
-	
-	private void iLog(String action, String args){
-		InteractionLog.log("GRAPH",action,args);
-	}
-	
-	public Edge edgeRef;
-	private Node nodeRef;
-	private Component parentComponent;
-	private Object[] arrowHeads;
-	private DiagramModelUpdater modelUpdater;
-	private static ResourceBundle resources = ResourceBundle.getBundle(EdgePopupMenu.class.getName()); 
-}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EdgePopupMenu.properties	Mon Feb 06 12:54:06 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-
-
-menu.set_label=Set Label
-menu.choose_arrow_head=Set Arrow Head
-menu.set_name=Set Name
-
-dialog.lock_failure.end=Edge end is being edited by another user
-dialog.lock_failure.name=Edge name is being edited by another user
-
-dialog.input.label=Enter Label Text
-dialog.input.arrow=Choose Arrow Head
-dialog.input.arrow.title=Select
-dialog.input.name=Renaming {0}, Enter new name 
\ No newline at end of file
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java	Wed Apr 25 17:09:09 2012 +0100
@@ -1,6 +1,6 @@
 /*  
  CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-	  
+
  Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
  Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
 
@@ -16,7 +16,7 @@
 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+ */
 
 package uk.ac.qmul.eecs.ccmi.gui;
 
@@ -75,19 +75,25 @@
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.PropertyMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.PropertyTypeMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.TypeMutableTreeNode;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.BroadcastFilter;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter;
 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
 import uk.ac.qmul.eecs.ccmi.haptics.Haptics;
 import uk.ac.qmul.eecs.ccmi.haptics.HapticsFactory;
+import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage;
 import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager;
+import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.RmDiagramRequest;
+import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.SendAwarenessRequest;
 import uk.ac.qmul.eecs.ccmi.network.Command;
 import uk.ac.qmul.eecs.ccmi.network.DiagramDownloader;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
 import uk.ac.qmul.eecs.ccmi.network.DiagramShareException;
 import uk.ac.qmul.eecs.ccmi.network.NetDiagram;
 import uk.ac.qmul.eecs.ccmi.network.ProtocolFactory;
@@ -103,1788 +109,2005 @@
 import uk.ac.qmul.eecs.ccmi.utils.Validator;
 
 /**
-*  The main frame of the editor which contains diagram panes that show graphs and tree representations of diagrams.
-*/
+ *  The main frame of the editor which contains diagram panes that show graphs and 
+ *  tree representations of diagrams.
+ */
 @SuppressWarnings("serial")
 public class EditorFrame extends JFrame {
-/**
-      Constructs a blank frame with a desktop pane
-      but no graph windows.
-      @param appClassName the fully qualified app class name.
-      It is expected that the resources are appClassName + "Strings"
-      and appClassName + "Version" (the latter for version-specific
-      resources)
-   */
-   public EditorFrame(Haptics haptics, File[] templateFiles, String backupDirPath, TemplateEditor[] templateEditors){  
-	  this.backupDirPath = backupDirPath; 
-	  /* load resources */ 
-	  resources = ResourceBundle.getBundle(this.getClass().getName());      
-	  
-	  /* haptics */
-	  this.haptics = haptics;
-      hapticTrigger = new HapticTrigger();
-      
-      /* read editor related preferences */
-      preferences = PreferencesService.getInstance();
-      
-      setIconImage(new ResourceFactory.ImageFactory().getImage("ccmi_favicon.gif"));
-      String laf = preferences.get("laf", null);
-      if (laf != null) changeLookAndFeel(laf);
+	/**
+	 * Creates a new {@code EditorFrame}
+	 * 
+	 * @param haptics an instance of {@code Haptics} handling the haptic device during this 
+	 * @param templateFiles an array of template files. New diagrams can be created from template files by clonation 
+	 * @param backupDirPath the path of a folder where all the currently open diagrams will be saved if 
+	 * the haptic device crashes  
+	 * @param templateEditors
+	 */
+	public EditorFrame(Haptics haptics, File[] templateFiles, String backupDirPath, TemplateEditor[] templateEditors){  
+		this.backupDirPath = backupDirPath; 
+		/* load resources */ 
+		resources = ResourceBundle.getBundle(this.getClass().getName());      
 
-      recentFiles = new ArrayList<String>(); 
-      File lastDir = new File(".");
-      String recent = preferences.get("recent", "").trim();
-      if (recent.length() > 0){
-         recentFiles.addAll(Arrays.asList(recent.split("[|]")));         
-         lastDir = new File(recentFiles.get(0)).getParentFile();
-      }
-      fileService = new FileService.ChooserService(lastDir);
-      
-      /* set up extensions */
-      defaultExtension = resources.getString("files.extension");
-      extensionFilter = new ExtensionFilter(
-         resources.getString("files.name"), 
-         new String[] { defaultExtension });
-      exportFilter = new ExtensionFilter(
-         resources.getString("files.image.name"), 
-         resources.getString("files.image.extension"));
-      
-	   /* start building the GUI */
-	   editorTabbedPane = new EditorTabbedPane(this);
-	   setContentPane(editorTabbedPane);
-	   
-	   
-	   setTitle(resources.getString("app.name"));
-	   Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+		/* haptics */
+		this.haptics = haptics;
+		hapticTrigger = new HapticTrigger();
 
-	   int screenWidth = (int)screenSize.getWidth();
-	   int screenHeight = (int)screenSize.getHeight();
+		/* read editor related preferences */
+		preferences = PreferencesService.getInstance();
 
-	   setLocation(screenWidth / 16, screenHeight / 16);
-	   editorTabbedPane.setPreferredSize(new Dimension(
-			   screenWidth * 5 / 8, screenHeight * 5 / 8));
+		setIconImage(new ResourceFactory.ImageFactory().getImage("ccmi_favicon.gif"));
+		changeLookAndFeel(preferences.get("laf", null));
 
-	   /* install the player listener (a narrator speech) for CANCEL, EMPTY and MESSAGE ok event. They are  *  
-	    * not depending on what the user is doing and the speech is therefore always the same. We only need *  
-	    * to set the listener once and it will do the job each time the sound is triggered                  */
-	   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-		   @Override
-		   public void playEnded() {
-			   DiagramPanel dPanel = getActiveTab();
-			   if(dPanel != null){// we can cancel a dialog even when no diagram is open (e.g. for tcp connections)
-				   speakFocusedComponent("");
-			   }else{
-				   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.cancelled"),""));
-			   }
-		   }
-	   }, SoundEvent.CANCEL);
-	   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-		   @Override
-		   public void playEnded() {
-			   DiagramPanel dPanel = getActiveTab();
-			   DiagramTree tree = dPanel.getTree();
-			   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.empty_property"),tree.currentPathSpeech()));
-		   }
-	   }, SoundEvent.EMPTY);
-	   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-		   @Override
-		   public void playEnded() {
-			   DiagramPanel dPanel = getActiveTab();
-			   DiagramTree tree = dPanel.getTree();
-			   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-		   }
-	   }, SoundEvent.MESSAGE_OK);
-	   
-	   /* setup listeners */ 
-	   initListeners();
-	   /* set up menus */  
-	   this.templateEditors = templateEditors;
-	   existingTemplateNames = new ArrayList<String>(10);
-	   existingTemplates = new ArrayList<Diagram>(10);
-	   int extensionLength = resources.getString("template.extension").length();
-	   for(File file : templateFiles){
-		   existingTemplateNames.add(file.getName().substring(0, file.getName().length()-extensionLength));
-	   }
-	   initMenu();
-	   /* read template files. this call must be placed after menu creation as it adds menu items to file->new-> */
-	   boolean someTemplateFilesNotRead = readTemplateFiles(templateFiles);
-	   /* become visible */
-	   pack();
-	   setVisible(true);
-	   /* if some templates were not read successfully, warn the user with a message */
-	   if(someTemplateFilesNotRead){
-		   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.filesnotread"), SpeechOptionPane.WARNING_MESSAGE);
-	   }
-   }
-   
-   private void initListeners(){
-	   /* window closing */
-	   setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
-	   addWindowListener(new  WindowAdapter(){
-		   @Override
-		   public void windowClosing(WindowEvent event){
-			   exit();
-		   }
-		   @Override
-		   public void	windowOpened(WindowEvent e) {
-			   // bring the window to front, else the openGL window would have higher priority
-			   e.getWindow().toFront();
-		   }
-	   });
+		recentFiles = new ArrayList<String>(); 
+		File lastDir = new File(".");
+		String recent = preferences.get("recent", "").trim();
+		if (recent.length() > 0){
+			recentFiles.addAll(Arrays.asList(recent.split("[|]")));         
+			lastDir = new File(recentFiles.get(0)).getParentFile();
+		}
+		fileService = new FileService.ChooserService(lastDir);
 
-	   addWindowFocusListener(new WindowFocusListener(){
-		   @Override
-		   public void windowGainedFocus(WindowEvent evt) {
-			   if(evt.getOppositeWindow() == null) 
-				   NarratorFactory.getInstance().speak(resources.getString("window.focus"));
-		   }
+		/* set up extensions */
+		defaultExtension = resources.getString("files.extension");
+		extensionFilter = new ExtensionFilter(
+				resources.getString("files.name"), 
+				new String[] { defaultExtension });
+		exportFilter = new ExtensionFilter(
+				resources.getString("files.image.name"), 
+				resources.getString("files.image.extension"));
 
-		   @Override
-		   public void windowLostFocus(WindowEvent evt) {
-			   if(evt.getOppositeWindow() == null)
-				   NarratorFactory.getInstance().speak(resources.getString("window.unfocus"));
-		   }
-	   });
+		/* start building the GUI */
+		editorTabbedPane = new EditorTabbedPane(this);
+		setContentPane(editorTabbedPane);
 
-	   /* set up listeners reacting to change of selection in the tree and tab */
-	   tabChangeListener = new ChangeListener(){
-		   @Override
-		   public void stateChanged(ChangeEvent evt) {
-			   DiagramPanel diagramPanel = getActiveTab();
-			   if (diagramPanel != null){ 
-				   /* give the focus to the Content Pane, else it's grabbed by the rootPane */
-			 	   getContentPane().requestFocusInWindow();
-				   TreePath path = diagramPanel.getTree().getSelectionPath();
-				   treeEnabledMenuUpdate(path);
-				   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex())));
 
-				   // updated the haptics
-				   int newTabId = editorTabbedPane.getSelectedIndex();
-				   HapticsFactory.getInstance().switchDiagram(newTabId);
-				   iLog("diagram tab changed to "+editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex()));
-			   }else{
-				   treeEnabledMenuUpdate(null);
-			   }
-			   /* if we change tab, the haptic highlight must be set again */
-			   selectHapticHighligh(null);
-			   /* so do the menu depending on the panel */
-			   diagramPanelEnabledMenuUpdate(diagramPanel);
-		   }
-	   };
-	   editorTabbedPane.addChangeListener(tabChangeListener);
+		setTitle(resources.getString("app.name"));
+		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 
-	   treeSelectionListener = new TreeSelectionListener(){
-		   @Override
-		   public void valueChanged(TreeSelectionEvent evt) {
-			   treeEnabledMenuUpdate(evt.getPath());
-		   }
-	   };
-	   
-	   netLocalDiagramExceptionHandler = new ExceptionHandler(){
-		   @Override
-		   public void handleException(Exception e) {
-			   SwingUtilities.invokeLater(new Runnable(){
-				@Override
-				public void run() {
-					SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.local_server"));
+		int screenWidth = (int)screenSize.getWidth();
+		int screenHeight = (int)screenSize.getHeight();
+
+		setLocation(screenWidth / 16, screenHeight / 16);
+		editorTabbedPane.setPreferredSize(new Dimension(
+				screenWidth * 5 / 8, screenHeight * 5 / 8));
+
+		/* install the player listener (a narrator speech) for CANCEL, EMPTY and MESSAGE ok event. They are  *  
+		 * not depending on what the user is doing and the speech is therefore always the same. We only need *  
+		 * to set the listener once and it will do the job each time the sound is triggered                  */
+		SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){
+			@Override
+			public void playEnded() {
+				DiagramPanel dPanel = getActiveTab();
+				if(dPanel != null){// we can cancel a dialog even when no diagram is open (e.g. for tcp connections)
+					speakFocusedComponent("");
+				}else{
+					NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.cancelled"),""));
 				}
-			   });
-		   }
-	   };
-   }
-   
-   private void initMenu(){
-	   ResourceFactory factory = new ResourceFactory(resources);
+			}
+		}, SoundEvent.CANCEL);
+		SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){
+			@Override
+			public void playEnded() {
+				DiagramPanel dPanel = getActiveTab();
+				DiagramTree tree = dPanel.getTree();
+				NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.empty_property"),tree.currentPathSpeech()));
+			}
+		}, SoundEvent.EMPTY);
+		SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){
+			@Override
+			public void playEnded() {
+				DiagramPanel dPanel = getActiveTab();
+				if(dPanel != null){
+					DiagramTree tree = dPanel.getTree();
+					NarratorFactory.getInstance().speak(tree.currentPathSpeech());
+				}
+			}
+		}, SoundEvent.MESSAGE_OK);
 
-	   JMenuBar menuBar = factory.createMenuBar();
-	   setJMenuBar(menuBar);
-	   
-	   /* --- FILE MENU --- */
-	   JMenu fileMenu = factory.createMenu("file");
-	   menuBar.add(fileMenu);
+		/* setup listeners */ 
+		initListeners();
+		/* set up menus */  
+		this.templateEditors = templateEditors;
+		existingTemplateNames = new ArrayList<String>(10);
+		existingTemplates = new ArrayList<Diagram>(10);
+		int extensionLength = resources.getString("template.extension").length();
+		for(File file : templateFiles){
+			existingTemplateNames.add(file.getName().substring(0, file.getName().length()-extensionLength));
+		}
+		initMenu();
+		/* read template files. this call must be placed after menu creation as it adds menu items to file->new-> */
+		boolean someTemplateFilesNotRead = readTemplateFiles(templateFiles);
+		/* become visible */
+		pack();
+		setVisible(true);
+		/* if some templates were not read successfully, warn the user with a message */
+		if(someTemplateFilesNotRead){
+			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.filesnotread"), SpeechOptionPane.WARNING_MESSAGE);
+		}
+	}
 
-	   /* menu items and listener added by addDiagramType function */
-	   newMenu = factory.createMenu("file.new");
-	   fileMenu.add(newMenu);
+	private void initListeners(){
+		/* window closing */
+		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+		addWindowListener(new  WindowAdapter(){
+			@Override
+			public void windowClosing(WindowEvent event){
+				exit();
+			}
+			@Override
+			public void	windowOpened(WindowEvent e) {
+				// bring the window to front, else the openGL window would have higher priority
+				e.getWindow().toFront();
+			}
+		});
 
-	   JMenuItem fileOpenItem = factory.createMenuItem(
-			   "file.open", this, "openFile"); 
-	   fileMenu.add(fileOpenItem);      
+		addWindowFocusListener(new WindowFocusListener(){
+			@Override
+			public void windowGainedFocus(WindowEvent evt) {
+				if(evt.getOppositeWindow() == null) 
+					NarratorFactory.getInstance().speak(resources.getString("window.focus"));
+			}
 
-	   recentFilesMenu = factory.createMenu("file.recent");
-	   buildRecentFilesMenu();
-	   fileMenu.add(recentFilesMenu);
+			@Override
+			public void windowLostFocus(WindowEvent evt) {
+				if(evt.getOppositeWindow() == null)
+					NarratorFactory.getInstance().speak(resources.getString("window.unfocus"));
+			}
+		});
 
-	   fileSaveItem = factory.createMenuItem("file.save", this, "saveFile");
-	   fileMenu.add(fileSaveItem);
-	   
-	   fileSaveAsItem = factory.createMenuItem("file.save_as", this, "saveFileAs");
-	   fileMenu.add(fileSaveAsItem);
+		/* set up listeners reacting to change of selection in the tree and tab */
+		tabChangeListener = new ChangeListener(){
+			@Override
+			public void stateChanged(ChangeEvent evt) {
+				DiagramPanel diagramPanel = getActiveTab();
+				if (diagramPanel != null){ 
+					/* give the focus to the Content Pane, otherwise it's grabbed by the rootPane */
+					getContentPane().requestFocusInWindow();
+					TreePath path = diagramPanel.getTree().getSelectionPath();
+					treeEnabledMenuUpdate(path);
+					NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex())));
 
-	   fileCloseItem = factory.createMenuItem("file.close",this,"closeFile");
-	   fileMenu.add(fileCloseItem);
-	   
-	   graphExportItem = factory.createMenuItem("file.export_image", this, "exportImage"); 
-	   fileMenu.add(graphExportItem);
-	   
-	   JMenuItem fileExitItem = factory.createMenuItem(
-			   "file.exit", this, "exit");
-	   fileMenu.add(fileExitItem);
+					// updated the haptics
+					HapticsFactory.getInstance().switchDiagram(diagramPanel.getDiagram().getName());
+					iLog("diagram tab changed to "+editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex()));
+				}else{
+					treeEnabledMenuUpdate(null);
+				}
+				/* if we change tab, the haptic highlight must be set again */
+				selectHapticHighligh(null);
+				/* so do the menu depending on the panel */
+				diagramPanelEnabledMenuUpdate(diagramPanel);
+			}
+		};
+		editorTabbedPane.addChangeListener(tabChangeListener);
 
-	   JMenu editMenu = factory.createMenu("edit");
-	   menuBar.add(editMenu);
+		treeSelectionListener = new TreeSelectionListener(){
+			@Override
+			public void valueChanged(TreeSelectionEvent evt) {
+				treeEnabledMenuUpdate(evt.getPath());
+			}
+		};
 
-	   locateMenuItem = factory.createMenuItem("edit.locate", this,"locate");
-	   editMenu.add(locateMenuItem);
-			
-	   highlightMenuItem = factory.createMenuItem("edit.highlight", this, "highlight");
-	   highlightMenuItem.setEnabled(false);
-	   editMenu.add(highlightMenuItem);
-	   
-	   jumpMenuItem = factory.createMenuItem("edit.jump",this,"jump");
-	   editMenu.add(jumpMenuItem);
+		netLocalDiagramExceptionHandler = new ExceptionHandler(){
+			@Override
+			public void handleException(Exception e) {
+				SwingUtilities.invokeLater(new Runnable(){
+					@Override
+					public void run() {
+						SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.local_server"));
+					}
+				});
+			}
+		};
+	}
 
-	   insertMenuItem = factory.createMenuItem("edit.insert", this, "insert");
-	   editMenu.add(insertMenuItem);
+	private void initMenu(){
+		ResourceFactory factory = new ResourceFactory(resources);
 
-	   deleteMenuItem = factory.createMenuItem("edit.delete",this,"delete");
-	   editMenu.add(deleteMenuItem);
-	   
-	   renameMenuItem = factory.createMenuItem("edit.rename",this,"rename");
-	   editMenu.add(renameMenuItem);
-	   
-	   bookmarkMenuItem = factory.createMenuItem("edit.bookmark",this,"editBookmarks"); 
-	   editMenu.add(bookmarkMenuItem);  
-	   
-	   editNotesMenuItem = factory.createMenuItem("edit.edit_note",this,"editNotes"); 
-	   editMenu.add(editNotesMenuItem);
+		JMenuBar menuBar = factory.createMenuBar();
+		setJMenuBar(menuBar);
 
-	   /* --- VIEW MENU --- */
-	   JMenu viewMenu = factory.createMenu("view");
-	   menuBar.add(viewMenu);
+		/* --- FILE MENU --- */
+		JMenu fileMenu = factory.createMenu("file");
+		menuBar.add(fileMenu);
 
-	   viewMenu.add(factory.createMenuItem(
-			   "view.zoom_out", new
-			   ActionListener(){
-				   public void actionPerformed(ActionEvent event){
-					   DiagramPanel dPanel = getActiveTab(); 
-					   if (dPanel == null) 
-						   return;
-					   dPanel.getGraphPanel().changeZoom(-1);
-				   }
-			   }));
+		/* menu items and listener added by addDiagramType function */
+		newMenu = factory.createMenu("file.new");
+		fileMenu.add(newMenu);
 
-	   viewMenu.add(factory.createMenuItem(
-			   "view.zoom_in", new
-			   ActionListener(){
-				   public void actionPerformed(ActionEvent event){
-					   DiagramPanel dPanel = getActiveTab(); 
-					   if (dPanel == null) 
-						   return;
-					   dPanel.getGraphPanel().changeZoom(1);
-				   }
-			   }));
+		JMenuItem fileOpenItem = factory.createMenuItem(
+				"file.open", this, "openFile"); 
+		fileMenu.add(fileOpenItem);      
 
-	   viewMenu.add(factory.createMenuItem(
-			   "view.grow_drawing_area", new
-			   ActionListener(){
-				   public void actionPerformed(ActionEvent event){
-					   DiagramPanel dPanel = getActiveTab(); 
-					   if (dPanel == null) 
-						   return;
-					   GraphPanel gPanel = dPanel.getGraphPanel();
-					   Rectangle2D bounds = gPanel.getGraphBounds();
-					   bounds.add(gPanel.getBounds());
-					   gPanel.setMinBounds(new Rectangle2D.Double(0, 0, 
-							   GROW_SCALE_FACTOR * bounds.getWidth(), 
-							   GROW_SCALE_FACTOR * bounds.getHeight()));
-					   gPanel.revalidate();
-					   gPanel.repaint();
-				   }
-			   }));
+		recentFilesMenu = factory.createMenu("file.recent");
+		buildRecentFilesMenu();
+		fileMenu.add(recentFilesMenu);
 
-	   viewMenu.add(factory.createMenuItem(
-			   "view.clip_drawing_area", new
-			   ActionListener(){
-				   public void actionPerformed(ActionEvent event){
-					   DiagramPanel dPanel = getActiveTab(); 
-					   if (dPanel == null) 
-						   return;
-					   GraphPanel gPanel = dPanel.getGraphPanel();
-					   gPanel.setMinBounds(null); 
-					   gPanel.revalidate();
-					   gPanel.repaint();
-				   }
-			   }));
+		fileSaveItem = factory.createMenuItem("file.save", this, "saveFile");
+		fileMenu.add(fileSaveItem);
 
-	   viewMenu.add(factory.createMenuItem(
-			   "view.smaller_grid", new
-			   ActionListener()
-			   {
-				   public void actionPerformed(ActionEvent event)
-				   {
-					   DiagramPanel dPanel = getActiveTab(); 
-					   if (dPanel == null) 
-						   return;
-					   dPanel.getGraphPanel().changeGridSize(-1);
-				   }
-			   }));
+		fileSaveAsItem = factory.createMenuItem("file.save_as", this, "saveFileAs");
+		fileMenu.add(fileSaveAsItem);
 
-	   viewMenu.add(factory.createMenuItem(
-			   "view.larger_grid", new
-			   ActionListener(){
-				   public void actionPerformed(ActionEvent event){
-					   DiagramPanel dPanel = getActiveTab(); 
-					   if (dPanel == null) 
-						   return;
-					   dPanel.getGraphPanel().changeGridSize(1);
-				   }
-			   }));
+		fileCloseItem = factory.createMenuItem("file.close",this,"closeFile");
+		fileMenu.add(fileCloseItem);
 
-	   final JCheckBoxMenuItem hideGridItem;
-	   viewMenu.add(hideGridItem = (JCheckBoxMenuItem) factory.createCheckBoxMenuItem(
-			   "view.hide_grid", new
-			   ActionListener()
-			   {
-				   public void actionPerformed(ActionEvent event)
-				   {
-					   DiagramPanel dPanel = getActiveTab(); 
-					   if (dPanel == null) 
-						   return;
-					   JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) event.getSource();               
-					   dPanel.getGraphPanel().setHideGrid(menuItem.isSelected());
-				   }
-			   }));
+		graphExportItem = factory.createMenuItem("file.export_image", this, "exportImage"); 
+		fileMenu.add(graphExportItem);
 
-	   viewMenu.addMenuListener(new
-			  MenuListener(){   
-		   /* changes the checkbox according to the diagram selected */
-		   public void menuSelected(MenuEvent event){
-			   DiagramPanel dPanel = getActiveTab(); 
-			   if (dPanel == null) 
-				   return;
-			   hideGridItem.setSelected(dPanel.getGraphPanel().getHideGrid());
-		   }
-		   public void menuDeselected(MenuEvent event){}
-		   public void menuCanceled(MenuEvent event){}
-	   });
+		JMenuItem fileExitItem = factory.createMenuItem(
+				"file.exit", this, "exit");
+		fileMenu.add(fileExitItem);
 
-	   JMenu lafMenu = factory.createMenu("view.change_laf");
-	   viewMenu.add(lafMenu);
+		/* --- EDIT MENU --- */ 
+		JMenu editMenu = factory.createMenu("edit");
+		menuBar.add(editMenu);
 
-	   UIManager.LookAndFeelInfo[] infos =
-		   UIManager.getInstalledLookAndFeels();
-	   for (int i = 0; i < infos.length; i++){
-		   final UIManager.LookAndFeelInfo info = infos[i];
-		   JMenuItem item = SpeechMenuFactory.getMenuItem(info.getName());
-		   lafMenu.add(item);
-		   item.addActionListener(new
-				   ActionListener(){
-			   public void actionPerformed(ActionEvent event){
-				   String laf = info.getClassName();
-				   changeLookAndFeel(laf);
-				   preferences.put("laf", laf);
-			   }
-		   });
-	   }
-	   
-	   /* --- TEMPLATE --- */
-	   JMenu templateMenu = factory.createMenu("template");
-	   menuBar.add(templateMenu);
-	   
-	   for(final TemplateEditor templateEditor : templateEditors){
-		   templateMenu.add(factory.createMenuItemFromLabel(
-				   templateEditor.getLabelForNew(),
-				   new ActionListener(){
-					   @Override
-					   public void actionPerformed(ActionEvent evt) {
-						   Diagram diagram = templateEditor.createNew(EditorFrame.this, existingTemplateNames);
-						   if(diagram == null)
-							   return;
-						   try{
-							   saveDiagramTemplate(diagram);
-						   }catch(IOException ioe){
-							   SpeechOptionPane.showMessageDialog(
-									   EditorFrame.this, 
-									   resources.getString("dialog.error.save_template"));
-							   return;
-						   }
-						   addDiagramType(diagram);
-						   SpeechOptionPane.showMessageDialog(
-								   EditorFrame.this,
-								   MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
-								   SpeechOptionPane.INFORMATION_MESSAGE);
-						   SoundFactory.getInstance().play(SoundEvent.OK,null);
-					   }
-				   })
-		   );
-		   
-		   templateMenu.add(factory.createMenuItemFromLabel(
-				   templateEditor.getLabelForEdit(),
-				   new ActionListener(){
-					   @Override
-					   public void actionPerformed(ActionEvent evt){
-						   if(existingTemplates.isEmpty()){
-							   NarratorFactory.getInstance().speak(resources.getString("dialog.error.no_template_to_edit"));
-							   return;
-						   }
-						   Diagram[] diagrams = new Diagram[existingTemplates.size()];
-						   diagrams = existingTemplates.toArray(diagrams);
-						   Diagram selectedDiagram = (Diagram)SpeechOptionPane.showSelectionDialog(
-								   EditorFrame.this, 
-								   resources.getString("dialog.input.edit_diagram_template"), 
-								   diagrams, 
-								   diagrams[0]);
-						   
-						   if(selectedDiagram == null){
-							   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-							   return;
-						   }
-						   
-						   Diagram diagram = templateEditor.edit(EditorFrame.this, existingTemplateNames,selectedDiagram);
-						   if(diagram == null)
-							   return;
-						   try{
-							   saveDiagramTemplate(diagram);
-						   }catch(IOException ioe){
-							   SpeechOptionPane.showMessageDialog(
-									   EditorFrame.this, 
-									   resources.getString("dialog.error.save_template"));
-							   return;
-						   }
-						   addDiagramType(diagram);
-						   SpeechOptionPane.showMessageDialog(
-								   EditorFrame.this,
-								   MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
-								   SpeechOptionPane.INFORMATION_MESSAGE);
-						   SoundFactory.getInstance().play(SoundEvent.OK,null);
-					   }
-				   }
-				   ));
-	   }
-	   
-	   /* --- SOUND --- */
-	   JMenu soundMenu = factory.createMenu("sound");
-	   menuBar.add(soundMenu);
-	   
-	   JMenuItem muteMenuItem = factory.createCheckBoxMenuItem("sound.mute", new ActionListener(){
-		   @Override
-		   public void actionPerformed(ActionEvent evt) {
-			   JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();               
-			   NarratorFactory.getInstance().setMuted(menuItem.isSelected());
-			   SoundFactory.getInstance().setMuted(menuItem.isSelected());
-		   }
-	   });
-	   soundMenu.add(muteMenuItem);
-	   
-	   JMenuItem rateMenuItem = factory.createMenuItem("sound.rate", new ActionListener(){
-		   @Override
-		   public void actionPerformed(ActionEvent evt) {
-			   Integer newRate = SpeechOptionPane.showNarratorRateDialog(
-					   EditorFrame.this, 
-					   resources.getString("dialog.input.sound_rate"),
-					   NarratorFactory.getInstance().getRate(), 
-					   Narrator.MIN_RATE, 
-					   Narrator.MAX_RATE);
-			   if(newRate != null){
-				   NarratorFactory.getInstance().setRate(newRate);
-				   NarratorFactory.getInstance().speak(
-						   MessageFormat.format(
-								   resources.getString("dialog.speech_rate.message"),
-								   newRate));
-			   }else{
-				   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   }
-		   }
-	   });
-	   soundMenu.add(rateMenuItem);
-	   
-	   /* --- COLLABORATION ---- */
-	   JMenu collabMenu = factory.createMenu("collab"); 
-	   menuBar.add(collabMenu);
-	   
-	   startServer = factory.createMenuItem("collab.start_server", this, "startServer");
-	   collabMenu.add(startServer);
-	   
-	   stopServer = factory.createMenuItem("collab.stop_server", this, "stopServer");
-	   collabMenu.add(stopServer);
-	   stopServer.setEnabled(false);
-	   
-	   shareDiagramMenuItem = factory.createMenuItem("collab.share_diagram", this, "shareDiagram");
-	   collabMenu.add(shareDiagramMenuItem);
-	   
-	   collabMenu.add(factory.createMenuItem("collab.open_shared_diagram", this, "openSharedDiagram"));
-	   
-	   /* --- HELP --- */
-	   JMenu helpMenu = factory.createMenu("help");
-	   menuBar.add(helpMenu);
+		locateMenuItem = factory.createMenuItem("edit.locate", this,"locate");
+		editMenu.add(locateMenuItem);
 
-	   helpMenu.add(factory.createMenuItem(
-			   "help.about", this, "showAboutDialog"));
+		highlightMenuItem = factory.createMenuItem("edit.highlight", this, "hHighlight");
+		highlightMenuItem.setEnabled(false);
+		editMenu.add(highlightMenuItem);
 
-	   helpMenu.add(factory.createMenuItem(
-			   "help.license", this, "showLicense"));
-	   
-	   treeEnabledMenuUpdate(null);
-	   diagramPanelEnabledMenuUpdate(null);
-   }
-   
-   private void changeLookAndFeel(String lafName){
-      try{
-         UIManager.setLookAndFeel(lafName);
-         SwingUtilities.updateComponentTreeUI(EditorFrame.this);
-      }
-      catch (ClassNotFoundException ex) {}
-      catch (InstantiationException ex) {}
-      catch (IllegalAccessException ex) {}
-      catch (UnsupportedLookAndFeelException ex) {}
-   }
-   
-   /**
-    * Adds a file name to the "recent files" list and rebuilds the "recent files" menu. 
-    * @param newFile the file name to add
-    */
-   private void addRecentFile(final String newFile){
-      recentFiles.remove(newFile);
-      if (newFile == null || newFile.equals("")) return;
-      recentFiles.add(0, newFile);
-      buildRecentFilesMenu();
-   }
-   
-   /* speaks out the selected tree node if the tree is focused or the tab label if the tab is focused */
-   private void speakFocusedComponent(String message){
-	   message = (message == null) ? "" : message+"; ";//add a dot to pause the TTS
-		   
-	   DiagramPanel dPanel = getActiveTab();
-	   if(dPanel != null){
-		   String focusedComponent = null;
-		   if(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() instanceof JTree)
-			   focusedComponent = dPanel.getTree().currentPathSpeech();
-		   else 
-			   focusedComponent = MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getComponentTabTitle(dPanel));
-		   NarratorFactory.getInstance().speak(message+focusedComponent);
-	   }
-   }
-   /**
-    * Rebuilds the "recent files" menu.
-    */
-   private void buildRecentFilesMenu(){
-      recentFilesMenu.removeAll();
-      for (int i = 0; i < recentFiles.size(); i++){
-         final String file =  recentFiles.get(i); 
-         String name = new File(file).getName();
-         JMenuItem item = SpeechMenuFactory.getMenuItem(name);
-         item.setToolTipText(file);
-         recentFilesMenu.add(item);
-         item.addActionListener(new
-               ActionListener(){
-                  public void actionPerformed(ActionEvent event){
-                	  try {
+		jumpMenuItem = factory.createMenuItem("edit.jump",this,"jump");
+		editMenu.add(jumpMenuItem);
+
+		insertMenuItem = factory.createMenuItem("edit.insert", this, "insert");
+		editMenu.add(insertMenuItem);
+
+		deleteMenuItem = factory.createMenuItem("edit.delete",this,"delete");
+		editMenu.add(deleteMenuItem);
+
+		renameMenuItem = factory.createMenuItem("edit.rename",this,"rename");
+		editMenu.add(renameMenuItem);
+
+		selectMenuItem = factory.createMenuItem("edit.select", this, "selectNode");
+		editMenu.add(selectMenuItem);
+
+		bookmarkMenuItem = factory.createMenuItem("edit.bookmark",this,"editBookmarks"); 
+		editMenu.add(bookmarkMenuItem);  
+
+		editNotesMenuItem = factory.createMenuItem("edit.edit_note",this,"editNotes"); 
+		editMenu.add(editNotesMenuItem);
+
+		/* --- VIEW MENU --- */
+		JMenu viewMenu = factory.createMenu("view");
+		menuBar.add(viewMenu);
+
+		viewMenu.add(factory.createMenuItem(
+				"view.zoom_out", new
+				ActionListener(){
+					public void actionPerformed(ActionEvent event){
+						DiagramPanel dPanel = getActiveTab(); 
+						if (dPanel == null) 
+							return;
+						dPanel.getGraphPanel().changeZoom(-1);
+					}
+				}));
+
+		viewMenu.add(factory.createMenuItem(
+				"view.zoom_in", new
+				ActionListener(){
+					public void actionPerformed(ActionEvent event){
+						DiagramPanel dPanel = getActiveTab(); 
+						if (dPanel == null) 
+							return;
+						dPanel.getGraphPanel().changeZoom(1);
+					}
+				}));
+
+		viewMenu.add(factory.createMenuItem(
+				"view.grow_drawing_area", new
+				ActionListener(){
+					public void actionPerformed(ActionEvent event){
+						DiagramPanel dPanel = getActiveTab(); 
+						if (dPanel == null) 
+							return;
+						GraphPanel gPanel = dPanel.getGraphPanel();
+						Rectangle2D bounds = gPanel.getGraphBounds();
+						bounds.add(gPanel.getBounds());
+						gPanel.setMinBounds(new Rectangle2D.Double(0, 0, 
+								GROW_SCALE_FACTOR * bounds.getWidth(), 
+								GROW_SCALE_FACTOR * bounds.getHeight()));
+						gPanel.revalidate();
+						gPanel.repaint();
+					}
+				}));
+
+		viewMenu.add(factory.createMenuItem(
+				"view.clip_drawing_area", new
+				ActionListener(){
+					public void actionPerformed(ActionEvent event){
+						DiagramPanel dPanel = getActiveTab(); 
+						if (dPanel == null) 
+							return;
+						GraphPanel gPanel = dPanel.getGraphPanel();
+						gPanel.setMinBounds(null); 
+						gPanel.revalidate();
+						gPanel.repaint();
+					}
+				}));
+
+		viewMenu.add(factory.createMenuItem(
+				"view.smaller_grid", new
+				ActionListener()
+				{
+					public void actionPerformed(ActionEvent event)
+					{
+						DiagramPanel dPanel = getActiveTab(); 
+						if (dPanel == null) 
+							return;
+						dPanel.getGraphPanel().changeGridSize(-1);
+					}
+				}));
+
+		viewMenu.add(factory.createMenuItem(
+				"view.larger_grid", new
+				ActionListener(){
+					public void actionPerformed(ActionEvent event){
+						DiagramPanel dPanel = getActiveTab(); 
+						if (dPanel == null) 
+							return;
+						dPanel.getGraphPanel().changeGridSize(1);
+					}
+				}));
+
+		final JCheckBoxMenuItem hideGridItem;
+		viewMenu.add(hideGridItem = (JCheckBoxMenuItem) factory.createCheckBoxMenuItem(
+				"view.hide_grid", new
+				ActionListener()
+				{
+					public void actionPerformed(ActionEvent event)
+					{
+						DiagramPanel dPanel = getActiveTab(); 
+						if (dPanel == null) 
+							return;
+						JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) event.getSource();               
+						dPanel.getGraphPanel().setHideGrid(menuItem.isSelected());
+					}
+				}));
+
+		viewMenu.addMenuListener(new
+				MenuListener(){   
+			/* changes the checkbox according to the diagram selected */
+			public void menuSelected(MenuEvent event){
+				DiagramPanel dPanel = getActiveTab(); 
+				if (dPanel == null) 
+					return;
+				hideGridItem.setSelected(dPanel.getGraphPanel().getHideGrid());
+			}
+			public void menuDeselected(MenuEvent event){}
+			public void menuCanceled(MenuEvent event){}
+		});
+
+		JMenu lafMenu = factory.createMenu("view.change_laf");
+		viewMenu.add(lafMenu);
+
+		UIManager.LookAndFeelInfo[] infos =
+			UIManager.getInstalledLookAndFeels();
+		for (int i = 0; i < infos.length; i++){
+			final UIManager.LookAndFeelInfo info = infos[i];
+			JMenuItem item = SpeechMenuFactory.getMenuItem(info.getName());
+			lafMenu.add(item);
+			item.addActionListener(new
+					ActionListener(){
+				public void actionPerformed(ActionEvent event){
+					String laf = info.getClassName();
+					changeLookAndFeel(laf);
+					preferences.put("laf", laf);
+				}
+			});
+		}
+
+		/* --- TEMPLATE --- */
+		JMenu templateMenu = factory.createMenu("template");
+		menuBar.add(templateMenu);
+
+		for(final TemplateEditor templateEditor : templateEditors){
+			templateMenu.add(factory.createMenuItemFromLabel(
+					templateEditor.getLabelForNew(),
+					new ActionListener(){
+						@Override
+						public void actionPerformed(ActionEvent evt) {
+							Diagram diagram = templateEditor.createNew(EditorFrame.this, existingTemplateNames);
+							if(diagram == null)
+								return;
+							try{
+								saveDiagramTemplate(diagram);
+							}catch(IOException ioe){
+								SpeechOptionPane.showMessageDialog(
+										EditorFrame.this, 
+										resources.getString("dialog.error.save_template"));
+								return;
+							}
+							addDiagramType(diagram);
+							SpeechOptionPane.showMessageDialog(
+									EditorFrame.this,
+									MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
+									SpeechOptionPane.INFORMATION_MESSAGE);
+							SoundFactory.getInstance().play(SoundEvent.OK,null);
+						}
+					})
+			);
+
+			templateMenu.add(factory.createMenuItemFromLabel(
+					templateEditor.getLabelForEdit(),
+					new ActionListener(){
+						@Override
+						public void actionPerformed(ActionEvent evt){
+							if(existingTemplates.isEmpty()){
+								NarratorFactory.getInstance().speak(resources.getString("dialog.error.no_template_to_edit"));
+								return;
+							}
+							Diagram[] diagrams = new Diagram[existingTemplates.size()];
+							diagrams = existingTemplates.toArray(diagrams);
+							Diagram selectedDiagram = (Diagram)SpeechOptionPane.showSelectionDialog(
+									EditorFrame.this, 
+									resources.getString("dialog.input.edit_diagram_template"), 
+									diagrams, 
+									diagrams[0]);
+
+							if(selectedDiagram == null){
+								SoundFactory.getInstance().play(SoundEvent.CANCEL);
+								return;
+							}
+
+							Diagram diagram = templateEditor.edit(EditorFrame.this, existingTemplateNames,selectedDiagram);
+							if(diagram == null)
+								return;
+							try{
+								saveDiagramTemplate(diagram);
+							}catch(IOException ioe){
+								SpeechOptionPane.showMessageDialog(
+										EditorFrame.this, 
+										resources.getString("dialog.error.save_template"));
+								return;
+							}
+							addDiagramType(diagram);
+							SpeechOptionPane.showMessageDialog(
+									EditorFrame.this,
+									MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
+									SpeechOptionPane.INFORMATION_MESSAGE);
+							SoundFactory.getInstance().play(SoundEvent.OK,null);
+						}
+					}
+			));
+		}
+
+		/* --- COLLABORATION ---- */
+		JMenu collabMenu = factory.createMenu("collab"); 
+		menuBar.add(collabMenu);
+
+		startServerMenuItem = factory.createMenuItem("collab.start_server", this, "startServer");
+		collabMenu.add(startServerMenuItem);
+
+		stopServerMenuItem = factory.createMenuItem("collab.stop_server", this, "stopServer");
+		collabMenu.add(stopServerMenuItem);
+		stopServerMenuItem.setEnabled(false);
+
+		shareDiagramMenuItem = factory.createMenuItem("collab.share_diagram", this, "shareDiagram");
+		collabMenu.add(shareDiagramMenuItem);
+
+		collabMenu.add(factory.createMenuItem("collab.open_shared_diagram", this, "openSharedDiagram"));
+
+		showAwarenessPanelMenuItem = factory.createMenuItem("collab.show_awareness_panel", this, "showAwarenessPanel");
+		collabMenu.add(showAwarenessPanelMenuItem);
+
+		hideAwarenessPanelMenuItem = factory.createMenuItem("collab.hide_awareness_panel", this, "hideAwarenessPanel");
+		collabMenu.add(hideAwarenessPanelMenuItem);
+
+		awarenessPanelListener = new AwarenessPanelEnablingListener(){
+			@Override
+			public void awarenessPanelEnabled(boolean enabled) {
+				if(enabled){
+					showAwarenessPanelMenuItem.setEnabled(true);
+					hideAwarenessPanelMenuItem.setEnabled(false);
+				}else{
+					showAwarenessPanelMenuItem.setEnabled(false);
+					hideAwarenessPanelMenuItem.setEnabled(false);
+				}
+			}
+
+			@Override
+			public void awarenessPanelVisible(boolean visible) {
+				if(visible){
+					showAwarenessPanelMenuItem.setEnabled(false);
+					hideAwarenessPanelMenuItem.setEnabled(true);
+				}else{
+					showAwarenessPanelMenuItem.setEnabled(true);
+					hideAwarenessPanelMenuItem.setEnabled(false);
+				}
+			}
+		};
+
+		/* --- PREFERENCES --- */
+		JMenu preferencesMenu = factory.createMenu("preferences");
+		menuBar.add(preferencesMenu);
+
+		// awareness menu 
+		JMenu awarenessMenu = factory.createMenu("preferences.awareness");
+		preferencesMenu.add(awarenessMenu);
+
+		awarenessMenu.add(factory.createMenuItem("preferences.awareness.username", this, "showAwarnessUsernameDialog")); 
+		awarenessMenu.add(factory.createMenuItem("preferences.awareness.broadcast", this, "showAwarenessBroadcastDialog"));
+		awarenessMenu.add(factory.createMenuItem("preferences.awareness.display", this, "showAwarenessDisplayDialog"));
+
+		JMenuItem enableAwarenessVoiceMenuItem = factory.createCheckBoxMenuItem("preferences.awareness.enable_voice",new ActionListener(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();
+				NarratorFactory.getInstance().setMuted(!menuItem.isSelected(),Narrator.SECOND_VOICE);
+				PreferencesService.getInstance().put("second_voice_enabled", Boolean.toString(menuItem.isSelected()));
+			}
+		});
+		NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE);
+		enableAwarenessVoiceMenuItem.setSelected(
+				Boolean.parseBoolean(PreferencesService.getInstance().get("second_voice_enabled", Boolean.toString(true))));
+		awarenessMenu.add(enableAwarenessVoiceMenuItem);
+		NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE);
+
+		//  sound 
+		JMenu soundMenu = factory.createMenu("preferences.sound");
+		preferencesMenu.add(soundMenu);
+
+		JMenuItem muteMenuItem = factory.createCheckBoxMenuItem("preferences.sound.mute", new ActionListener(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();               
+				NarratorFactory.getInstance().setMuted(menuItem.isSelected(),Narrator.FIRST_VOICE);
+				SoundFactory.getInstance().setMuted(menuItem.isSelected());
+			}
+		});
+		soundMenu.add(muteMenuItem);
+
+		JMenuItem rateMenuItem = factory.createMenuItem("preferences.sound.rate", new ActionListener(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				Integer newRate = SpeechOptionPane.showNarratorRateDialog(
+						EditorFrame.this, 
+						resources.getString("dialog.input.sound_rate"),
+						NarratorFactory.getInstance().getRate(), 
+						Narrator.MIN_RATE,
+						Narrator.MAX_RATE);
+				if(newRate != null){
+					NarratorFactory.getInstance().setRate(newRate);
+					NarratorFactory.getInstance().speak(
+							MessageFormat.format(
+									resources.getString("dialog.feedback.speech_rate"),
+									newRate));
+				}else{
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				}
+			}
+		});
+		soundMenu.add(rateMenuItem);
+
+		//server ports
+		JMenu networkingMenu = factory.createMenu("preferences.server");
+		preferencesMenu.add(networkingMenu);
+
+		JMenuItem localPortMenuItem = factory.createMenuItem("preferences.local_server.port", this, "showLocalServerPortDialog");
+		networkingMenu.add(localPortMenuItem);
+
+		JMenuItem remotePortMenuItem = factory.createMenuItem("preferences.remote_server.port", this, "showRemoteServerPortDialog");
+		networkingMenu.add(remotePortMenuItem);
+
+		// accessible file chooser
+		JMenuItem fileChooserMenuItem = factory.createCheckBoxMenuItem("preferences.file_chooser", new ActionListener(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();
+				PreferencesService.getInstance().put("use_accessible_filechooser", Boolean.toString(menuItem.isSelected()));
+			}
+		});
+
+		// temporarily mute the narrator to select the menu without triggering a speech 
+		NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE);
+		fileChooserMenuItem.setSelected(Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true")));
+		NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE);
+		preferencesMenu.add(fileChooserMenuItem);
+
+
+		/* --- HELP --- */
+		JMenu helpMenu = factory.createMenu("help");
+		menuBar.add(helpMenu);
+
+		helpMenu.add(factory.createMenuItem(
+				"help.about", this, "showAboutDialog"));
+
+		helpMenu.add(factory.createMenuItem(
+				"help.license", this, "showLicense"));
+
+		treeEnabledMenuUpdate(null);
+		diagramPanelEnabledMenuUpdate(null);
+	}
+
+	private void changeLookAndFeel(String lafName){
+		if(lafName == null)
+			lafName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
+		try{
+			UIManager.setLookAndFeel(lafName);
+			SwingUtilities.updateComponentTreeUI(EditorFrame.this);
+		}
+		catch (ClassNotFoundException ex) {}
+		catch (InstantiationException ex) {}
+		catch (IllegalAccessException ex) {}
+		catch (UnsupportedLookAndFeelException ex) {}
+	}
+
+	/**
+	 * Adds a file name to the "recent files" list and rebuilds the "recent files" menu. 
+	 * @param newFile the file name to add
+	 */
+	private void addRecentFile(final String newFile){
+		recentFiles.remove(newFile);
+		if (newFile == null || newFile.equals("")) return;
+		recentFiles.add(0, newFile);
+		buildRecentFilesMenu();
+	}
+
+	/* speaks out the selected tree node if the tree is focused or the tab label if the tab is focused */
+	private void speakFocusedComponent(String message){
+		message = (message == null) ? "" : message+"; ";//add a dot to pause the TTS
+		DiagramPanel dPanel = getActiveTab();
+		if(dPanel != null){
+			String focusedComponent = null;
+			if(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() instanceof JTree)
+				focusedComponent = dPanel.getTree().currentPathSpeech();
+			else 
+				focusedComponent = MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getComponentTabTitle(dPanel));
+			NarratorFactory.getInstance().speak(message+focusedComponent);
+		}
+	}
+	/**
+	 * Rebuilds the "recent files" menu.
+	 */
+	private void buildRecentFilesMenu(){
+		recentFilesMenu.removeAll();
+		for (int i = 0; i < recentFiles.size(); i++){
+			final String file =  recentFiles.get(i); 
+			String name = new File(file).getName();
+			JMenuItem item = SpeechMenuFactory.getMenuItem(name);
+			item.setToolTipText(file);
+			recentFilesMenu.add(item);
+			item.addActionListener(new
+					ActionListener(){
+				public void actionPerformed(ActionEvent event){
+					try {
 						FileService.Open open = new FileService.DirectService().open(new File(((JMenuItem)event.getSource()).getToolTipText()));
 						InputStream in = open.getInputStream();
-				    	  String path = open.getPath();
-				    	  for(int i=0; i<editorTabbedPane.getTabCount();i++){
-				    		  if(path.equals(editorTabbedPane.getToolTipTextAt(i))){
-				    			  editorTabbedPane.setSelectedIndex(i);
-				    			  return;
-				    		  }
-				    	  }
-				    	  Diagram diagram = PersistenceManager.decodeDiagramInstance(in);
-				    	  addTab(open.getPath(), diagram);
+						String path = open.getPath();
+						for(int i=0; i<editorTabbedPane.getTabCount();i++){
+							if(path.equals(editorTabbedPane.getToolTipTextAt(i))){
+								editorTabbedPane.setSelectedIndex(i);
+								return;
+							}
+						}
+						Diagram diagram = PersistenceManager.decodeDiagramInstance(in);
+						addTab(open.getPath(), diagram);
 					} catch (IOException exception) {
 						SpeechOptionPane.showMessageDialog(
-				        		 editorTabbedPane, 
-				        		 exception.getLocalizedMessage()); 
+								editorTabbedPane, 
+								exception.getLocalizedMessage()); 
 					}
-                  }
-               });
-      }      
-   }
+				}
+			});
+		}      
+	}
 
-   /**
+	/**
       Asks the user to open a graph file.
-   */
-   public void openFile(){  
-	  InputStream in = null; 
-      try{
-    	  FileService.Open open = fileService.open(null,null, extensionFilter,this);
-    	  in = open.getInputStream();
-    	  if(in != null){ // open.getInputStream() == null -> user clicked on cancel
-	    	  String path = open.getPath();
-	    	  int index = editorTabbedPane.getPathTabIndex(path);
-	    	  if(index != -1){ //diagram is already open
-	    		  editorTabbedPane.setSelectedIndex(index);
-	    		  speakFocusedComponent("");
-	    		  return;
-	    	  }
-	    	  /* every opened diagram must have a unique name */
-	    	  if(editorTabbedPane.getDiagramNameTabIndex(open.getName()) != -1)
-	    			  throw new IOException(resources.getString("dialog.error.same_file_name"));
-    		  iLog("START READ LOCAL DIAGRAM "+open.getName());
-	    	  Diagram diagram = PersistenceManager.decodeDiagramInstance(in);
-	    	  iLog("END READ LOCAL DIAGRAM "+open.getName());
-	    	  /* force the name of the diagram to be the same as the file name       *
-	    	   * it should never be useful, unless the .ccmi file is edited manually */
-	    	  diagram.setName(open.getName());
-	    	  addTab(open.getPath(), diagram);
-	    	  addRecentFile(open.getPath());
-    	  }
-      }
-      catch (IOException exception)      {
-         SpeechOptionPane.showMessageDialog(
-        		 editorTabbedPane, 
-        		 exception.getLocalizedMessage()); 
-      }finally{
-    	  if(in != null)
-    		  try{in.close();}catch(IOException ioe){ioe.printStackTrace();}
-    		  
-      }
-   }
-   
-   public void closeFile(){
-	   DiagramPanel dPanel = getActiveTab();
-	   if(dPanel.isModified()||dPanel.getFilePath() == null){
-		   int answer = SpeechOptionPane.showConfirmDialog(
-				   EditorFrame.this, 
-				   resources.getString("dialog.confirm.close"), 
-				   SpeechOptionPane.YES_NO_OPTION);
-		   
-		   if(answer == SpeechOptionPane.YES_OPTION) // save file only if the user decides to
-			   if(!saveFile()) 
-				   return; /* if the user closes the save dialog do nothing */
-	   }
-	   iLog("diagram closed :"+dPanel.getDiagram().getName());
-	   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.diagram_closed"),dPanel.getDiagram().getName()));
-	   editorTabbedPane.remove(dPanel);
-   }
-   
-   public boolean saveFile(){  
-      DiagramPanel diagramPanel = getActiveTab();
-      if (diagramPanel == null) // no tabs open  
-    	  return false;
-      String fileName = diagramPanel.getFilePath();
-      if (fileName == null) { 
-    	  return saveFileAs(); 
-      }
-      
-      OutputStream out = null;
-      try{
-      	File file = new File(fileName);
-      	out = new BufferedOutputStream(new FileOutputStream(file));
-      	Diagram d = diagramPanel.getDiagram();
-      	PersistenceManager.encodeDiagramInstance(d, out);
-      	/* we saved the diagram, therefore there are no more pending changes */
-      	diagramPanel.setModified(false);
-      	speakFocusedComponent(resources.getString("dialog.file_saved"));
-      	return true;
-      }catch(IOException ioe){
-    	  SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
-    	  return false;
-      }finally{
-    	  try {
-    		  out.close();
-          }catch(IOException ioe){ /*can't do anything */ }
-      }        
-   }
-   
-   /**
+	 */
+	public void openFile(){  
+		InputStream in = null; 
+		try{
+			FileService.Open open = fileService.open(null,null, extensionFilter,this);
+			in = open.getInputStream();
+			if(in != null){ // open.getInputStream() == null -> user clicked on cancel
+				String path = open.getPath();
+				int index = editorTabbedPane.getPathTabIndex(path);
+				if(index != -1){ //diagram is already open
+					editorTabbedPane.setSelectedIndex(index);
+					speakFocusedComponent("");
+					return;
+				}
+				/* every opened diagram must have a unique name */
+				if(editorTabbedPane.getDiagramNameTabIndex(open.getName()) != -1)
+					throw new IOException(resources.getString("dialog.error.same_file_name"));
+				iLog("START READ LOCAL DIAGRAM "+open.getName());
+				Diagram diagram = PersistenceManager.decodeDiagramInstance(in);
+				iLog("END READ LOCAL DIAGRAM "+open.getName());
+				/* force the name of the diagram to be the same as the file name       *
+				 * it should never be useful, unless the .ccmi file is edited manually */
+				diagram.setName(open.getName());
+				addTab(open.getPath(), diagram);
+				addRecentFile(open.getPath());
+			}
+		}
+		catch (IOException exception)      {
+			SpeechOptionPane.showMessageDialog(
+					editorTabbedPane, 
+					exception.getLocalizedMessage()); 
+		}finally{
+			if(in != null)
+				try{in.close();}catch(IOException ioe){ioe.printStackTrace();}
+		}
+	}
+
+	public void closeFile(){
+		DiagramPanel dPanel = getActiveTab();
+		if(dPanel.isModified()||dPanel.getFilePath() == null){
+			int answer = SpeechOptionPane.showConfirmDialog(
+					EditorFrame.this, 
+					resources.getString("dialog.confirm.close"), 
+					SpeechOptionPane.YES_NO_OPTION);
+
+			if(answer == SpeechOptionPane.YES_OPTION) // save file only if the user decides to
+				if(!saveFile()) 
+					return; /* if the user closes the save dialog do nothing */
+		}
+		iLog("diagram closed :"+dPanel.getDiagram().getName());
+		NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.diagram_closed"),dPanel.getDiagram().getName()));
+		if(dPanel.getDiagram() instanceof NetDiagram){
+			if(clientConnectionManager != null && clientConnectionManager.isAlive())
+				clientConnectionManager.addRequest(new RmDiagramRequest(dPanel.getDiagram().getName()));
+		}
+		editorTabbedPane.remove(dPanel);
+		//getActiveTab, after removing, returns the new selected panel after the remotion, if any  
+		String newFocusedTabName = null;
+		if(getActiveTab() != null){
+			newFocusedTabName = getActiveTab().getDiagram().getName();
+		}
+		haptics.removeDiagram(dPanel.getDiagram().getName(), newFocusedTabName);
+	}
+
+	public boolean saveFile(){  
+		DiagramPanel diagramPanel = getActiveTab();
+		if (diagramPanel == null) // no tabs open  
+			return false;
+		String fileName = diagramPanel.getFilePath();
+		if (fileName == null) { 
+			return saveFileAs(); 
+		}
+
+		OutputStream out = null;
+		try{
+			File file = new File(fileName);
+			out = new BufferedOutputStream(new FileOutputStream(file));
+			Diagram d = diagramPanel.getDiagram();
+			PersistenceManager.encodeDiagramInstance(d, out);
+			/* we saved the diagram, therefore there are no more pending changes */
+			diagramPanel.setModified(false);
+			speakFocusedComponent(resources.getString("dialog.file_saved"));
+			return true;
+		}catch(IOException ioe){
+			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
+			return false;
+		}finally{
+			try {
+				out.close();
+			}catch(IOException ioe){ /*can't do anything */ }
+		}        
+	}
+
+	/**
       Saves the current graph as a new file.
-   */
-   public boolean saveFileAs() { 
-      DiagramPanel diagramPanel = getActiveTab();
-      if (diagramPanel == null) // no tabs open  
-    	  return false;
-      OutputStream out = null;
-      try {
-	      FileService.Save save = fileService.save(
-	    		  PreferencesService.getInstance().get("dir.diagrams", "."),
-	    		  diagramPanel.getFilePath(), 
-	    		  extensionFilter, 
-	    		  null, 
-	    		  defaultExtension);
-	      out = save.getOutputStream();
-	      if (out == null)   /* user didn't select any file for saving */
-	    	  return false;
+	 */
+	public boolean saveFileAs() { 
+		DiagramPanel diagramPanel = getActiveTab();
+		if (diagramPanel == null) // no tabs open  
+			return false;
+		OutputStream out = null;
+		try {
+			String[] currentTabs = new String[editorTabbedPane.getTabCount()];
+			for(int i=0; i<editorTabbedPane.getTabCount();i++){
+				currentTabs[i] = editorTabbedPane.getTitleAt(i);
+			}
+			FileService.Save save = fileService.save(
+					PreferencesService.getInstance().get("dir.diagrams", "."),
+					diagramPanel.getFilePath(), 
+					extensionFilter, 
+					null, 
+					defaultExtension,
+					currentTabs);
+			out = save.getOutputStream();
+			if (out == null)   /* user didn't select any file for saving */
+				return false;
 
-	      String fileName = save.getName();
-		  /* there cannot be two diagrams with the same name open at the same time */
-	      if(editorTabbedPane.getDiagramNameTabIndex(save.getName()) != -1 && !fileName.equals(diagramPanel.getName()))
-			   throw new IOException(resources.getString("dialog.error.same_file_name"));
-    	
-    	  PersistenceManager.encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
-    	  /* update the diagram panel, after the saving */
-    	  diagramPanel.setFilePath(save.getPath());
-   		  diagramPanel.setModified(false);
-   		  speakFocusedComponent(resources.getString("dialog.file_saved"));
-    	  return true;
-      }catch(IOException ioe){
-    	  SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
-    	  return false;
-      }finally{
-    	  if(out != null)
-    		  try{out.close();}catch(IOException ioe){ioe.printStackTrace();}
-      }
-   }
+			PersistenceManager.encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
+			/* update the diagram panel, after the saving */
+			diagramPanel.setFilePath(save.getPath());
+			diagramPanel.setModified(false);
+			speakFocusedComponent(resources.getString("dialog.file_saved"));
+			return true;
+		}catch(IOException ioe){
+			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
+			return false;
+		}finally{
+			if(out != null)
+				try{out.close();}catch(IOException ioe){ioe.printStackTrace();}
+		}
+	}
 
-   /**
+	/**
       Exits the program if no graphs have been modified
       or if the user agrees to abandon modified graphs.
-   */
-   public void exit(){
-	   /* check first whether there are modified diagrams */
-	   int diagramsToSave = 0;
-	   for(int i=0; i<editorTabbedPane.getTabCount();i++){
-		   DiagramPanel dPanel = editorTabbedPane.getComponentAt(i); 
-		   if(dPanel.isModified()||dPanel.getFilePath() == null){
-			   diagramsToSave++;
-		   }
-	   }
+	 */
+	public void exit(){
+		/* check first whether there are modified diagrams */
+		int diagramsToSave = 0;
+		for(int i=0; i<editorTabbedPane.getTabCount();i++){
+			DiagramPanel dPanel = editorTabbedPane.getComponentAt(i); 
+			if(dPanel.isModified()||dPanel.getFilePath() == null){
+				diagramsToSave++;
+			}
+		}
 
-	   if(diagramsToSave > 0){
-		   int answer = SpeechOptionPane.showConfirmDialog(
-				   EditorFrame.this, 
-				   MessageFormat.format(resources.getString("dialog.confirm.exit"), diagramsToSave),
-				   SpeechOptionPane.YES_NO_OPTION);
-		   // if the doesn't want to save changes, veto the close
-		   if(answer != SpeechOptionPane.NO_OPTION){
-			   if(answer == SpeechOptionPane.YES_OPTION){ // user clicked on yes button we just get them back to the editor
-				   speakFocusedComponent("");
-			   }else{// user pressed the ESC button
-				   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   }
-			   return;
-		   }
-	   }
+		if(diagramsToSave > 0){
+			int answer = SpeechOptionPane.showConfirmDialog(
+					EditorFrame.this, 
+					MessageFormat.format(resources.getString("dialog.confirm.exit"), diagramsToSave),
+					SpeechOptionPane.YES_NO_OPTION);
+			// if the doesn't want to save changes, veto the close
+			if(answer != SpeechOptionPane.NO_OPTION){
+				if(answer == SpeechOptionPane.YES_OPTION){ // user clicked on yes button we just get them back to the editor
+					speakFocusedComponent("");
+				}else{// user pressed the ESC button
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				}
+				return;
+			}
+		}
 
-	   NarratorFactory.getInstance().dispose();
-	   SoundFactory.getInstance().dispose();
-	   haptics.dispose();
-	   if(server != null)
-		   server.shutdown();
-	   if(clientConnectionManager != null)
-		   clientConnectionManager.shutdown();
-	   while(haptics.isAlive()){/* wait */}
-	   /* closes the logger's handlers */
-	   iLog("PROGRAM EXIT");
-	   InteractionLog.dispose();
+		NarratorFactory.getInstance().dispose();
+		SoundFactory.getInstance().dispose();
+		haptics.dispose();
+		if(server != null)
+			server.shutdown();
+		if(clientConnectionManager != null)
+			clientConnectionManager.shutdown();
+		BroadcastFilter broadcastFilter = BroadcastFilter.getInstance();
+		if(broadcastFilter != null)
+			broadcastFilter.saveProperties(this);
+		DisplayFilter displayFilter = DisplayFilter.getInstance();
+		if(displayFilter != null)
+			displayFilter.saveProperties(this);
 
-	   savePreferences();
-	   System.exit(0);
-   }
-   
-   public void jump(){
-	   String[] options = new String[canJumpRef ? 4 : 3];
-	   options[0] = resources.getString("options.jump.type");
-	   options[1] = resources.getString("options.jump.diagram");
-	   options[2] = resources.getString("options.jump.bookmark");
-	   if(canJumpRef){
-		   options[3] = resources.getString("options.jump.reference");
-	   }
-	   iLog("open jump to dialog","");
-	   String result = (String)SpeechOptionPane.showSelectionDialog(
-			   	EditorFrame.this,
-			   	resources.getString("dialog.input.jump.select"),
-			   	options, 
-	   			options[0]);
-	   DiagramPanel dPanel = getActiveTab(); 
-	   DiagramTree tree = dPanel.getTree(); 
-	   if(result != null){
-		   if(result.equals(options[0])){ // jump type
-			   tree.jump(DiagramTree.JumpTo.SELECTED_TYPE);
-		   }else if(result.equals(options[1])){// diagram
-			   tree.jump(DiagramTree.JumpTo.ROOT);
-		   }else if(result.equals(options[2])){// bookmark
-			   tree.jump(DiagramTree.JumpTo.BOOKMARK);
-		   }else if(result.equals(options[3])){ 
-			   tree.jump(DiagramTree.JumpTo.REFERENCE);
-		   }
-	   }else{
-		   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-		   iLog("cancel jump to dialog","");
-	   }
-   }
-   
-   public void locate(){
+		while(haptics.isAlive()){/* wait */}
+		/* closes the logger's handlers */
+		iLog("PROGRAM EXIT");
+		InteractionLog.dispose();
+
+		savePreferences();
+		System.exit(0);
+	}
+
+	public void jump(){
+		String[] options = new String[canJumpRef ? 4 : 3];
+		options[0] = resources.getString("options.jump.type");
+		options[1] = resources.getString("options.jump.diagram");
+		options[2] = resources.getString("options.jump.bookmark");
+		if(canJumpRef){
+			options[3] = resources.getString("options.jump.reference");
+		}
+		iLog("open jump to dialog","");
+		String result = (String)SpeechOptionPane.showSelectionDialog(
+				EditorFrame.this,
+				resources.getString("dialog.input.jump.select"),
+				options, 
+				options[0]);
+		DiagramPanel dPanel = getActiveTab(); 
+		DiagramTree tree = dPanel.getTree(); 
+		if(result != null){
+			if(result.equals(options[0])){ // jump type
+				tree.jump(DiagramTree.JumpTo.SELECTED_TYPE);
+			}else if(result.equals(options[1])){// diagram
+				tree.jump(DiagramTree.JumpTo.ROOT);
+			}else if(result.equals(options[2])){// bookmark
+				tree.jump(DiagramTree.JumpTo.BOOKMARK);
+			}else if(result.equals(options[3])){ 
+				tree.jump(DiagramTree.JumpTo.REFERENCE);
+			}
+		}else{
+			SoundFactory.getInstance().play(SoundEvent.CANCEL);
+			iLog("cancel jump to dialog","");
+		}
+	}
+
+	public void locate(){
 		DiagramPanel dPanel = getActiveTab();
 		DiagramTree tree = dPanel.getTree();
 		DiagramElement de = (DiagramElement)tree.getSelectionPath().getLastPathComponent();
 		HapticsFactory.getInstance().attractTo(System.identityHashCode(de));
 		iLog("locate " +((de instanceof Node)? "node" : "edge"),DiagramElement.toLogString(de));
-   }
-   
-   public void highlight() {
+	}
+
+	public void hHighlight() {
 		getActiveTab().getTree().jumpTo(hapticHighlightDiagramElement);
 		iLog("highlight " +((hapticHighlightDiagramElement instanceof Node)? "node" : "edge"),DiagramElement.toLogString(hapticHighlightDiagramElement));
-   }
+	}
 
-   public void insert(){
-	   DiagramPanel dPanel = getActiveTab();
-	   final DiagramTree tree = dPanel.getTree();
-	   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)tree.getSelectionPath().getLastPathComponent();
-	   DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
-	   if(treeNode instanceof TypeMutableTreeNode){ //adding a diagram Element
-		   TypeMutableTreeNode typeNode = (TypeMutableTreeNode)treeNode;
-		   final DiagramElement diagramElement = (DiagramElement)typeNode.getPrototype().clone();
-		   try {
-			   if(diagramElement instanceof Edge){
-				   Edge edge = (Edge)diagramElement; 
-				   edge.connect(Arrays.asList(tree.getSelectedNodes()));
-				   iLog("insert edge",DiagramElement.toLogString(edge));
-				   modelUpdater.insertInTree(diagramElement);
+	public void hPickUp(DiagramElement de) {
+		HapticsFactory.getInstance().pickUp(System.identityHashCode(de));
+	}
 
-				   final StringBuilder builder = new StringBuilder();
-				   for(int i=0; i<edge.getNodesNum();i++){
-					   if(i == edge.getNodesNum()-1)
-						   builder.append(edge.getNodeAt(i)+resources.getString("speech.input.edge.ack"));
-					   else
-						   builder.append(edge.getNodeAt(i)+ resources.getString("speech.input.edge.ack2"));
-				   }
-				   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-					   @Override
-					   public void playEnded() {
-						   NarratorFactory.getInstance().speak(builder.toString());
-					   }
-				   }, SoundEvent.OK);
-				   SoundFactory.getInstance().play(SoundEvent.OK);
-				   /* remove the selections on the edge's nodes and release their lock */
-				   tree.clearNodeSelections();
-				   for(int i=0; i<edge.getNodesNum();i++){
-					   modelUpdater.yieldLock(edge, Lock.MUST_EXIST);
-				   }
-			   }else{ // adding a Node 
-				   iLog("insert node ",DiagramElement.toLogString(diagramElement));
-				   modelUpdater.insertInTree(diagramElement);
-				   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-					   @Override
-					   public void playEnded() {
-						   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.input.node.ack"),diagramElement.spokenText()));
-					   }
-				   }, SoundEvent.OK);
-				   SoundFactory.getInstance().play(SoundEvent.OK);
-			   }
-		   } catch (ConnectNodesException cne) {
-			   final String message = cne.getLocalizedMessage();
-			   SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
-				   @Override
-				   public void playEnded() {
-					   NarratorFactory.getInstance().speak(message);
-				   }
-			   });
-			   SoundFactory.getInstance().play(SoundEvent.ERROR);
-			   iLog("insert edge error",message);
-		   }
-	   }else if(treeNode instanceof PropertyTypeMutableTreeNode){ //adding a property
-		   PropertyTypeMutableTreeNode propTypeNode = (PropertyTypeMutableTreeNode)treeNode;
-		   Node n = (Node)propTypeNode.getNode();
-		   if(!modelUpdater.getLock(n, Lock.PROPERTIES)){
-			   iLog("Could not get lock on node for add properties",DiagramElement.toLogString(n));
-			   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
-			   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-			   return;
-		   }
-		   
-		   iLog("open insert property dialog","");
-		   final String propertyValue = SpeechOptionPane.showInputDialog(EditorFrame.this, 
-				   MessageFormat.format(resources.getString("dialog.input.property.text"),propTypeNode.getName())
-		   );
-		   if(propertyValue != null){
-			   if(!propertyValue.isEmpty()){ //check that the user didn't enter an empty string
-				   iLog("insert property ", propTypeNode.getType()+" "+propertyValue);
-				   modelUpdater.addProperty(n, propTypeNode.getType(), propertyValue);
-				   SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
-					   @Override
-					   public void playEnded() {
-						   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.input.property.ack"),propertyValue));
-					   }
-				   });
-			   }else{
-				   SoundFactory.getInstance().play(SoundEvent.EMPTY);
-				   iLog("insert property", "");
-			   }
-		   }else{ 
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   iLog("cancel insert property dialog","");
-		   }
-		   modelUpdater.yieldLock(n, Lock.PROPERTIES);
-	   }else if(treeNode instanceof PropertyMutableTreeNode){ // edit modifiers
-		   iLog("open modifiers dialog","");
-		   PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
-		   Node n = (Node)typeNode.getNode();
-		   Modifiers modifiers = n.getProperties().getModifiers(typeNode.getType());
-		   if(modifiers.isNull()){
-			   iLog("error:no modifiers for this property","");
-			   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("dialog.warning.null_modifiers"),typeNode.getType()));
-		   }else{
-			   if(!modelUpdater.getLock(n, Lock.PROPERTIES)){
-				   iLog("Could not get lock on node for set modifiers",DiagramElement.toLogString(n));
-				   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
-				   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-				   return;
-			   }
-			   
-			   int index = typeNode.getIndex(treeNode);
-			   Set<Integer> result = SpeechOptionPane.showModifiersDialog(EditorFrame.this, 
-					   MessageFormat.format(resources.getString("dialog.input.check_modifiers"),n.getProperties().getValues(typeNode.getType()).get(index)) , 
-					   modifiers.getTypes(), 
-					   modifiers.getIndexes(index)
-				  );
-			   if(result == null){
-				   iLog("cancel modifiers dialog","");
-				   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   }else{
-				   iLog("edit modifiers",Arrays.toString(result.toArray()));
-				   modelUpdater.setModifiers(n, typeNode.getType(), index, result);
-				   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-					   @Override
-					   public void playEnded() {
-						   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-					   }
-				   }, SoundEvent.OK);
-				   SoundFactory.getInstance().play(SoundEvent.OK);
-			   }
-			   modelUpdater.yieldLock(n, Lock.PROPERTIES);
-		   }
-	   }else{ //NodeReferenceMutableTreeNode = edit label and arrow head 
-		   NodeReferenceMutableTreeNode nodeRef = (NodeReferenceMutableTreeNode)treeNode;
-		   Node n = (Node)nodeRef.getNode();
-		   Edge e = (Edge)nodeRef.getEdge();
-		   if(!modelUpdater.getLock(e, Lock.EDGE_END)){
-			   iLog("Could not get lock on edge for end label",DiagramElement.toLogString(e));
-			   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.end_label"), SpeechOptionPane.INFORMATION_MESSAGE);
-			   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-			   return;
-		   }
-		   iLog("open edge operation selection dialog","");
-		   
-		   boolean hasAvailArrowHeads = (e.getAvailableEndDescriptions().length > 0);
-		   String[] operations = new String[hasAvailArrowHeads ? 2 : 1];
-		   operations[0] = resources.getString("dialog.input.edge_operation.label");
-		   if(hasAvailArrowHeads)
-			   operations[1] = resources.getString("dialog.input.edge_operation.arrow_head");
-		   String choice = (String)SpeechOptionPane.showSelectionDialog(
-				   EditorFrame.this,
-				   resources.getString("dialog.input.edge_operation.select"),
-				   operations,
-				   operations[0]);
+	public void insert(){
+		DiagramPanel dPanel = getActiveTab();
+		final DiagramTree tree = dPanel.getTree();
+		DiagramTreeNode treeNode = (DiagramTreeNode)tree.getSelectionPath().getLastPathComponent();
+		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
+		if(treeNode instanceof TypeMutableTreeNode){ //adding a diagram Element
+			TypeMutableTreeNode typeNode = (TypeMutableTreeNode)treeNode;
+			final DiagramElement diagramElement = (DiagramElement)typeNode.getPrototype().clone();
+			try {
+				if(diagramElement instanceof Edge){
+					Edge edge = (Edge)diagramElement; 
+					edge.connect(Arrays.asList(tree.getSelectedNodes()));
+					iLog("insert edge",DiagramElement.toLogString(edge));
+					modelUpdater.insertInTree(diagramElement);
 
-		   if(choice == null){
-			   iLog("cancel edge operation selection dialog","");
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   modelUpdater.yieldLock(e, Lock.EDGE_END);
-			   return;
-		   }
-		   if(choice.equals(operations[0])){  //operations[0] = edit edge end-label
-			   iLog("open edge label dialog","");
-			   String label = SpeechOptionPane.showInputDialog(
-					   EditorFrame.this,
-					   MessageFormat.format(resources.getString("dialog.input.edge_label"),n.getType(), n.getName()),
-					   e.getEndLabel(n) );
-			   if(label != null){
-				   modelUpdater.setEndLabel(e, n, label);
-				   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-					   @Override
-					   public void playEnded() {
-						   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-					   }
-				   }, SoundEvent.OK);
-				   SoundFactory.getInstance().play(SoundEvent.OK);
-			   }else{
-				   iLog("cancel edge label dialog","");
-				   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   }
-		   }else{//operations[1] = edit edge arrow head 
-			   String[] endDescriptions = new String[e.getAvailableEndDescriptions().length+1];
-			   for(int i=0;i<e.getAvailableEndDescriptions().length;i++)
-				   endDescriptions[i] = e.getAvailableEndDescriptions()[i];
-			   endDescriptions[endDescriptions.length-1] = Edge.NO_ARROW_STRING;
+					/* remove the selections on the edge's nodes and release their lock */
+					tree.clearNodeSelections();
+					for(int i=0; i<edge.getNodesNum();i++){
+						modelUpdater.yieldLock(edge, Lock.MUST_EXIST,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.INSERT_EDGE,edge.getId(),edge.getName()));
+					}
+				}else{ // adding a Node 
+					iLog("insert node ",DiagramElement.toLogString(diagramElement));
+					modelUpdater.insertInTree(diagramElement);
+				}
+			} catch (ConnectNodesException cne) {
+				final String message = cne.getLocalizedMessage();
+				SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
+					@Override
+					public void playEnded() {
+						NarratorFactory.getInstance().speak(message);
+					}
+				});
+				SoundFactory.getInstance().play(SoundEvent.ERROR);
+				iLog("insert edge error",message);
+			}
+		}else if(treeNode instanceof PropertyTypeMutableTreeNode){ //adding a property
+			PropertyTypeMutableTreeNode propTypeNode = (PropertyTypeMutableTreeNode)treeNode;
+			Node n = (Node)propTypeNode.getNode();
+			if(!modelUpdater.getLock(n, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.ADD_PROPERTY,n.getId(),n.getName()))){
+				iLog("Could not get lock on node for add properties",DiagramElement.toLogString(n));
+				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
+				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+				return;
+			}
 
-			   iLog("open edge arrow head dialog","");
-			   final String endDescription = (String)SpeechOptionPane.showSelectionDialog(
-					   EditorFrame.this,
-					   MessageFormat.format(resources.getString("dialog.input.edge_arrowhead"),n.getType(), n.getName()),
-					   endDescriptions,
-					   endDescriptions[0]
-			   );
-			   if(endDescription != null){
-				   int index = Edge.NO_END_DESCRIPTION_INDEX;
-				   for(int i=0;i<e.getAvailableEndDescriptions().length;i++)
-					   if(endDescription.equals(e.getAvailableEndDescriptions()[i])){
-						   index = i;
-						   break;
-					   }
-				   modelUpdater.setEndDescription(e, n, index);
-				   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-					   @Override
-					   public void playEnded() {
-						   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-					   }
-				   }, SoundEvent.OK);
-				   SoundFactory.getInstance().play(SoundEvent.OK);
-			   }else{
-				   iLog("cancel edge arrow head dialog","");
-				   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   }
-		   }
-		   modelUpdater.yieldLock(e, Lock.EDGE_END);
-	   }
-   }
+			iLog("open insert property dialog","");
+			final String propertyValue = SpeechOptionPane.showInputDialog(EditorFrame.this, 
+					MessageFormat.format(resources.getString("dialog.input.property.text"),propTypeNode.getName())
+			);
+			if(propertyValue != null){
+				if(!propertyValue.isEmpty()){ //check that the user didn't enter an empty string
+					iLog("insert property ", propTypeNode.getType()+" "+propertyValue);
+					modelUpdater.addProperty(n, propTypeNode.getType(), propertyValue, DiagramEventSource.TREE);
+				}else{
+					SoundFactory.getInstance().play(SoundEvent.EMPTY);
+					iLog("insert property", "");
+				}
+			}else{ 
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				iLog("cancel insert property dialog","");
+			}
+			modelUpdater.yieldLock(n, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.ADD_PROPERTY,n.getId(),n.getName()));
+		}else if(treeNode instanceof PropertyMutableTreeNode){ // edit modifiers
+			iLog("open modifiers dialog","");
+			PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
+			Node n = (Node)typeNode.getNode();
+			Modifiers modifiers = n.getProperties().getModifiers(typeNode.getType());
+			if(modifiers.isNull()){
+				iLog("error:no modifiers for this property","");
+				NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("dialog.warning.null_modifiers"),typeNode.getType()));
+			}else{
+				if(!modelUpdater.getLock(n, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_MODIFIERS,n.getId(),n.getName()))){
+					iLog("Could not get lock on node for set modifiers",DiagramElement.toLogString(n));
+					SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
+					SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+					return;
+				}
 
-   public void delete(){
-	   DiagramPanel dPanel = getActiveTab();
-	   final DiagramTree tree = dPanel.getTree();
-	   final DiagramModelTreeNode treeNode = (DiagramModelTreeNode)tree.getSelectionPath().getLastPathComponent();
-	   DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
-	   if(treeNode instanceof DiagramElement){ //delete a diagram element
-		   final DiagramElement element = (DiagramElement)treeNode;
-		   if(!modelUpdater.getLock(element, Lock.DELETE)){
-			   iLog("Could not get lock on element for deletion",DiagramElement.toLogString(element));
-			   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.delete"), SpeechOptionPane.INFORMATION_MESSAGE);
-			   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-			   return;
-		   }
-		   iLog("open delete "+ ((element instanceof Node)? "node" : "edge") +" dialog","");
-		   int choice = SpeechOptionPane.showConfirmDialog(
-				   EditorFrame.this, 
-				   MessageFormat.format(resources.getString("dialog.confirm.deletion"),element.getType(), element.getName()), 
-				   SpeechOptionPane.OK_CANCEL_OPTION);
-		   if(choice != SpeechOptionPane.OK_OPTION){
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   iLog("cancel delete " + ((element instanceof Node)? "node" : "edge") +" dialog","");
-			   modelUpdater.yieldLock(element, Lock.DELETE);
-			   return;
-		   }
-		   modelUpdater.takeOutFromTree(element);
-		   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-			   @Override
-			   public void playEnded() {
-				   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.delete.element.ack"),element.spokenText(),tree.currentPathSpeech()));
-			   }
-		   }, SoundEvent.OK);
-		   SoundFactory.getInstance().play(SoundEvent.OK);
-	   }else if(treeNode.getParent() instanceof PropertyTypeMutableTreeNode){ //deleting a property
-		   PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
-		   Node n = (Node)typeNode.getNode();
-		   if(!modelUpdater.getLock(n, Lock.PROPERTIES)){
-			   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
-			   iLog("Could not get lock for properties for deletion",DiagramElement.toLogString(n));
-			   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-			   return;
-		   }
-		   iLog("open delete property dialog","");
-		   int choice = SpeechOptionPane.showConfirmDialog(
-				   EditorFrame.this, 
-				   MessageFormat.format(resources.getString("dialog.confirm.deletion"),typeNode.getType(),treeNode.getName()), 
-				   SpeechOptionPane.OK_CANCEL_OPTION);
-		   if(choice != SpeechOptionPane.OK_OPTION){
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   iLog("cancel delete property dialog","");
-			   modelUpdater.yieldLock(n, Lock.PROPERTIES);
-			   return;
-		   }
-		   modelUpdater.removeProperty(n, typeNode.getType(), typeNode.getIndex(treeNode));
-		   SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
-			   @Override
-			   public void playEnded() {
-				   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.deleted.property.ack"),treeNode.spokenText(),tree.currentPathSpeech()));
-			   }
-		   });
-	   }else
-		   throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName());
-   }
-   
-   public void rename(){
-	   DiagramPanel dPanel = getActiveTab();
-	   DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
-	   final DiagramTree tree = dPanel.getTree();
-	   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)tree.getSelectionPath().getLastPathComponent();
-	   MessageFormat formatter = new MessageFormat(resources.getString("dialog.input.rename"));
-	   if(treeNode instanceof DiagramElement){
-		   DiagramElement element = (DiagramElement)dPanel.getTree().getSelectionPath().getLastPathComponent();
-		   Object arg[] = {element.getName()};
-		   if(!modelUpdater.getLock(element, Lock.NAME)){
-			   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.name"),SpeechOptionPane.INFORMATION_MESSAGE);
-			   iLog("Could not get lock on element for renaming",DiagramElement.toLogString(element));
-			   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-			   return;
-		   }
-		   iLog("open rename "+((element instanceof Node)? "node" : "edge")+" dialog",DiagramElement.toLogString(element));
-		   String name = SpeechOptionPane.showInputDialog(EditorFrame.this,
-				   formatter.format(arg),
-				   element.getName());
-		   if(name != null){
-			   modelUpdater.setName(element,name);
-			   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-				   @Override
-				   public void playEnded() {
-					   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-				   }
-			   }, SoundEvent.OK);
-			   SoundFactory.getInstance().play(SoundEvent.OK);
-		   }else{
-			   iLog("cancel rename "+((element instanceof Node)? "node" : "edge")+" dialog",DiagramElement.toLogString(element));
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-		   }
-		   modelUpdater.yieldLock(element, Lock.NAME);
-	   }else if(treeNode instanceof PropertyMutableTreeNode){
-		   PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
-		   Node n = (Node)typeNode.getNode();
-		   if(!modelUpdater.getLock(n, Lock.PROPERTIES)){
-			   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
-			   iLog("Could not get lock on properties for renaming",DiagramElement.toLogString(n));
-			   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-			   return;
-		   }
-		   Object arg[] = {treeNode.getName()};
-		   iLog("open rename property dialog",treeNode.getName());
-		   String name = SpeechOptionPane.showInputDialog(EditorFrame.this,
-				   formatter.format(arg),
-				   treeNode.getName());
-		   if(name == null){
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   iLog("cancel rename property dialog",treeNode.getName());
-			   return;
-		   }
-		   modelUpdater.setProperty(n, typeNode.getType(), typeNode.getIndex(treeNode), name);
-		   SoundFactory.getInstance().setPlayerListener(new PlayerListener(){
-			   @Override
-			   public void playEnded() {
-				   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-			   }
-		   }, SoundEvent.OK);
-		   SoundFactory.getInstance().play(SoundEvent.OK);
-		   modelUpdater.yieldLock(n, Lock.PROPERTIES);
-	   }else
-		   throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName());
-   }
-   
-   public void editBookmarks(){
-	   boolean addBookmark = true;
-	   DiagramPanel dPanel = getActiveTab(); 
-	   final DiagramTree tree = dPanel.getTree();
-	   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)tree.getLastSelectedPathComponent();
-	   DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
-	   
-	   if(!modelUpdater.getLock(treeNode, Lock.BOOKMARK)){
-		   iLog("Cannot get lock on tree node for bookmark", treeNode.getName());
-		   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.bookmark"), SpeechOptionPane.INFORMATION_MESSAGE);
-		   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-		   return;
-	   }
-	   
-	   if(!treeNode.getBookmarkKeys().isEmpty()){
-		   /* the are already bookmarks, thus we let the user chose whether they want to   */
-		   /* add a new one or remove an old one                                           */
-		   String[] options = {
-				   resources.getString("dialog.input.bookmark.select.add"),
-				   resources.getString("dialog.input.bookmark.select.remove")
-		   };
+				int index = typeNode.getIndex(treeNode);
+				Set<Integer> result = SpeechOptionPane.showModifiersDialog(EditorFrame.this, 
+						MessageFormat.format(resources.getString("dialog.input.check_modifiers"),n.getProperties().getValues(typeNode.getType()).get(index)) , 
+						modifiers.getTypes(), 
+						modifiers.getIndexes(index)
+				);
+				if(result == null){
+					iLog("cancel modifiers dialog","");
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				}else{
+					iLog("edit modifiers",Arrays.toString(result.toArray()));
+					modelUpdater.setModifiers(n, typeNode.getType(), index, result,DiagramEventSource.TREE);
+				}
+				modelUpdater.yieldLock(n, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_MODIFIERS,n.getId(),n.getName()));
+			}
+		}else{ //NodeReferenceMutableTreeNode = edit label and arrow head 
+			NodeReferenceMutableTreeNode nodeRef = (NodeReferenceMutableTreeNode)treeNode;
+			Node n = (Node)nodeRef.getNode();
+			Edge e = (Edge)nodeRef.getEdge();
+			if(!modelUpdater.getLock(e, Lock.EDGE_END,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName()))){
+				iLog("Could not get lock on edge for end label",DiagramElement.toLogString(e));
+				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.end_label"), SpeechOptionPane.INFORMATION_MESSAGE);
+				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+				return;
+			}
+			iLog("open edge operation selection dialog","");
 
-		   iLog("open select add/remove bookmark dialog","");
-		   String result = (String)SpeechOptionPane.showSelectionDialog(EditorFrame.this,
-				   resources.getString("dialog.input.bookmark.select.add_remove"), 
-				   options, 
-				   options[0]);
-		   
-		   if(result == null){
-			   iLog("cancel select add/remove bookmark dialog","");
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   modelUpdater.yieldLock(treeNode, Lock.BOOKMARK);
-			   return;
-		   }
-		   if(result.equals(options[1]))
-			   addBookmark = false;
-	   }
-	   
-	   if(addBookmark){
-		   boolean uniqueBookmarkChosen = false;
-		   while(!uniqueBookmarkChosen){
-			   iLog("open add bookmark dialog","");
-			   String bookmark = SpeechOptionPane.showInputDialog(EditorFrame.this, resources.getString("dialog.input.bookmark.text"));
-			   if(bookmark != null){
-				   if("".equals(bookmark)){
-					   iLog("error: entered empty bookmark","");
-					   SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
-						   @Override
-						   public void playEnded(){
-							   NarratorFactory.getInstance().speak(resources.getString("dialog.input.bookmark.text.empty"));
-						   }
-					   });
-				   }else if(tree.getModel().getBookmarks().contains(bookmark)){
-					   iLog("error: entered bookmark already existing",bookmark);
-					   SoundFactory.getInstance().play(SoundEvent.ERROR,new PlayerListener(){
-						   @Override
-						   public void playEnded() {
-							   NarratorFactory.getInstance().speak(resources.getString("dialog.input.bookmark.text.already_existing"));
-						   }
-					   });
-				   }else{
-					   tree.getModel().putBookmark(bookmark, treeNode);
-					   uniqueBookmarkChosen = true;
-					   SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
-						   @Override
-						   public void playEnded() {
-							   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-						   }
-					   });
-				   }
-			   }else{
-				   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-				   iLog("cancel add bookmark dialog","");
-				   break; //user no longer wants to choose, exit the dialog thus
-			   }
-		   }
-	   }else{ // removing a bookmark
-		   String[] bookmarksArray = new String[treeNode.getBookmarkKeys().size()];
+			boolean hasAvailArrowHeads = (e.getAvailableEndDescriptions().length > 0);
+			String[] operations = new String[hasAvailArrowHeads ? 2 : 1];
+			operations[0] = resources.getString("dialog.input.edge_operation.label");
+			if(hasAvailArrowHeads)
+				operations[1] = resources.getString("dialog.input.edge_operation.arrow_head");
+			String choice = (String)SpeechOptionPane.showSelectionDialog(
+					EditorFrame.this,
+					resources.getString("dialog.input.edge_operation.select"),
+					operations,
+					operations[0]);
+
+			if(choice == null){
+				iLog("cancel edge operation selection dialog","");
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				modelUpdater.yieldLock(e, Lock.EDGE_END,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName()));
+				return;
+			}
+			if(choice.equals(operations[0])){  //operations[0] = edit edge end-label
+				iLog("open edge label dialog","");
+				String label = SpeechOptionPane.showInputDialog(
+						EditorFrame.this,
+						MessageFormat.format(resources.getString("dialog.input.edge_label"),n.getType(), n.getName()),
+						e.getEndLabel(n) );
+				if(label != null){
+					modelUpdater.setEndLabel(e, n, label,DiagramEventSource.TREE);
+					SoundFactory.getInstance().play(SoundEvent.OK, new PlayerListener(){
+						@Override
+						public void playEnded() {
+							NarratorFactory.getInstance().speak(tree.currentPathSpeech());
+						}
+					});
+				}else{
+					iLog("cancel edge label dialog","");
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				}
+			}else{//operations[1] = edit edge arrow head 
+				String[] endDescriptions = new String[e.getAvailableEndDescriptions().length+1];
+				for(int i=0;i<e.getAvailableEndDescriptions().length;i++)
+					endDescriptions[i] = e.getAvailableEndDescriptions()[i];
+				endDescriptions[endDescriptions.length-1] = Edge.NO_ENDDESCRIPTION_STRING;
+
+				iLog("open edge arrow head dialog","");
+				final String endDescription = (String)SpeechOptionPane.showSelectionDialog(
+						EditorFrame.this,
+						MessageFormat.format(resources.getString("dialog.input.edge_arrowhead"),n.getType(), n.getName()),
+						endDescriptions,
+						endDescriptions[0]
+				);
+				if(endDescription != null){
+					int index = Edge.NO_END_DESCRIPTION_INDEX;
+					for(int i=0;i<e.getAvailableEndDescriptions().length;i++)
+						if(endDescription.equals(e.getAvailableEndDescriptions()[i])){
+							index = i;
+							break;
+						}
+					modelUpdater.setEndDescription(e, n, index,DiagramEventSource.TREE);
+				}else{
+					iLog("cancel edge arrow head dialog","");
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				}
+			}
+			modelUpdater.yieldLock(e, Lock.EDGE_END,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName()));
+		}
+	}
+
+	public void delete(){
+		DiagramPanel dPanel = getActiveTab();
+		final DiagramTree tree = dPanel.getTree();
+		final DiagramTreeNode treeNode = (DiagramTreeNode)tree.getSelectionPath().getLastPathComponent();
+		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
+		if(treeNode instanceof DiagramElement){ //delete a diagram element
+			final DiagramElement element = (DiagramElement)treeNode;
+			boolean isNode = element instanceof Node;
+			if(!modelUpdater.getLock(element, 
+					Lock.DELETE, 
+					new DiagramEventActionSource(DiagramEventSource.TREE,
+							isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
+									element.getId(),element.getName()))){
+				iLog("Could not get lock on element for deletion",DiagramElement.toLogString(element));
+				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.delete"), SpeechOptionPane.INFORMATION_MESSAGE);
+				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+				return;
+			}
+			iLog("open delete "+ (isNode ? "node" : "edge") +" dialog","");
+			int choice = SpeechOptionPane.showConfirmDialog(
+					EditorFrame.this, 
+					MessageFormat.format(resources.getString("dialog.confirm.deletion"),element.getType(), element.getName()), 
+					SpeechOptionPane.OK_CANCEL_OPTION);
+			if(choice != SpeechOptionPane.OK_OPTION){
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				iLog("cancel delete " + (isNode ? "node" : "edge") +" dialog","");
+				modelUpdater.yieldLock(element, 
+						Lock.DELETE,
+						new DiagramEventActionSource(DiagramEventSource.TREE,
+								isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
+										element.getId(),
+										element.getName()));
+				return;
+			}
+			modelUpdater.takeOutFromTree(element);
+			/* don't need to unlock because the object doesn't exist any more, but        *
+			 * still need to make other users aware that the deletion process is finished */
+			modelUpdater.sendAwarenessMessage(
+					AwarenessMessage.Name.STOP_A,
+					new DiagramEventActionSource(DiagramEventSource.TREE,
+							isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
+									element.getId(),element.getName())
+			);
+		}else if(treeNode.getParent() instanceof PropertyTypeMutableTreeNode){ //deleting a property
+			PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
+			Node n = (Node)typeNode.getNode();
+			if(!modelUpdater.getLock(n, 
+					Lock.PROPERTIES,
+					new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.REMOVE_PROPERTY,n.getId(),n.getName()))){
+				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
+				iLog("Could not get lock for properties for deletion",DiagramElement.toLogString(n));
+				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+				return;
+			}
+			iLog("open delete property dialog","");
+			int choice = SpeechOptionPane.showConfirmDialog(
+					EditorFrame.this, 
+					MessageFormat.format(resources.getString("dialog.confirm.deletion"),typeNode.getType(),treeNode.getName()), 
+					SpeechOptionPane.OK_CANCEL_OPTION);
+			if(choice != SpeechOptionPane.OK_OPTION){
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				iLog("cancel delete property dialog","");
+				modelUpdater.yieldLock(n, 
+						Lock.PROPERTIES,
+						new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.REMOVE_PROPERTY,n.getId(),n.getName()));
+				return;
+			}
+			modelUpdater.removeProperty(n, typeNode.getType(), typeNode.getIndex(treeNode),DiagramEventSource.TREE);
+		}else
+			throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName());
+	}
+
+	public void rename(){
+		DiagramPanel dPanel = getActiveTab();
+		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
+		final DiagramTree tree = dPanel.getTree();
+		DiagramTreeNode treeNode = (DiagramTreeNode)tree.getSelectionPath().getLastPathComponent();
+		MessageFormat formatter = new MessageFormat(resources.getString("dialog.input.rename"));
+		if(treeNode instanceof DiagramElement){
+			DiagramElement element = (DiagramElement)dPanel.getTree().getSelectionPath().getLastPathComponent();
+			Object arg[] = {element.getName()};
+			boolean isNode = element instanceof Node;
+			if(!modelUpdater.getLock(element, 
+					Lock.NAME,
+					new DiagramEventActionSource(DiagramEventSource.TREE,
+							isNode ? Command.Name.SET_NODE_NAME : Command.Name.SET_EDGE_NAME,element.getId(),element.getName()))){
+				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.name"),SpeechOptionPane.INFORMATION_MESSAGE);
+				iLog("Could not get lock on element for renaming",DiagramElement.toLogString(element));
+				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+				return;
+			}
+			iLog("open rename "+(isNode ? "node" : "edge")+" dialog",DiagramElement.toLogString(element));
+			String name = SpeechOptionPane.showInputDialog(EditorFrame.this,
+					formatter.format(arg),
+					element.getName());
+			if(name != null){
+				modelUpdater.setName(element,name,DiagramEventSource.TREE);
+			}else{
+				iLog("cancel rename "+(isNode ? "node" : "edge")+" dialog",DiagramElement.toLogString(element));
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+			}
+			modelUpdater.yieldLock(element, 
+					Lock.NAME,
+					new DiagramEventActionSource(
+							DiagramEventSource.TREE,
+							isNode ? Command.Name.SET_NODE_NAME : Command.Name.SET_EDGE_NAME,
+									element.getId(),
+									element.getName()));
+		}else if(treeNode instanceof PropertyMutableTreeNode){
+			PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
+			Node n = (Node)typeNode.getNode();
+			if(!modelUpdater.getLock(n, 
+					Lock.PROPERTIES, 
+					new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_PROPERTY,n.getId(),n.getName()))){
+				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
+				iLog("Could not get lock on properties for renaming",DiagramElement.toLogString(n));
+				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+				return;
+			}
+			Object arg[] = {treeNode.getName()};
+			iLog("open rename property dialog",treeNode.getName());
+			String name = SpeechOptionPane.showInputDialog(EditorFrame.this,
+					formatter.format(arg),
+					treeNode.getName());
+			if(name == null){
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				iLog("cancel rename property dialog",treeNode.getName());
+				return;
+			}
+			modelUpdater.setProperty(n, typeNode.getType(), typeNode.getIndex(treeNode), name,DiagramEventSource.TREE);
+			modelUpdater.yieldLock(n, 
+					Lock.PROPERTIES,
+					new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_PROPERTY,n.getId(),n.getName()));
+		}else
+			throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName());
+	}
+
+	public void editBookmarks(){
+		boolean addBookmark = true;
+		DiagramPanel dPanel = getActiveTab(); 
+		final DiagramTree tree = dPanel.getTree();
+		DiagramTreeNode treeNode = (DiagramTreeNode)tree.getLastSelectedPathComponent();
+		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
+
+		if(!modelUpdater.getLock(treeNode, 
+				Lock.BOOKMARK, 
+				DiagramEventActionSource.NULL)){
+			iLog("Cannot get lock on tree node for bookmark", treeNode.getName());
+			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.bookmark"), SpeechOptionPane.INFORMATION_MESSAGE);
+			SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+			return;
+		}
+
+		if(!treeNode.getBookmarkKeys().isEmpty()){
+			/* the are already bookmarks, thus we let the user chose whether they want to   */
+			/* add a new one or remove an old one                                           */
+			String[] options = {
+					resources.getString("dialog.input.bookmark.select.add"),
+					resources.getString("dialog.input.bookmark.select.remove")
+			};
+
+			iLog("open select add/remove bookmark dialog","");
+			String result = (String)SpeechOptionPane.showSelectionDialog(EditorFrame.this,
+					resources.getString("dialog.input.bookmark.select.add_remove"), 
+					options, 
+					options[0]);
+
+			if(result == null){
+				iLog("cancel select add/remove bookmark dialog","");
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				modelUpdater.yieldLock(treeNode, Lock.BOOKMARK, DiagramEventActionSource.NULL);
+				return;
+			}
+			if(result.equals(options[1]))
+				addBookmark = false;
+		}
+
+		if(addBookmark){
+			boolean uniqueBookmarkChosen = false;
+			while(!uniqueBookmarkChosen){
+				iLog("open add bookmark dialog","");
+				String bookmark = SpeechOptionPane.showInputDialog(EditorFrame.this, resources.getString("dialog.input.bookmark.text"));
+				if(bookmark != null){
+					if("".equals(bookmark)){
+						iLog("error: entered empty bookmark","");
+						SoundFactory.getInstance().play(SoundEvent.ERROR);// without listeners in order not to overwrite the speechdialog popping up again
+						NarratorFactory.getInstance().speakWholeText(resources.getString("dialog.input.bookmark.text.empty"));
+					}else if(tree.getModel().getBookmarks().contains(bookmark)){
+						iLog("error: entered bookmark already existing",bookmark);
+						SoundFactory.getInstance().play(SoundEvent.ERROR);
+						NarratorFactory.getInstance().speakWholeText(resources.getString("dialog.input.bookmark.text.already_existing"));
+					}else{
+						tree.getModel().putBookmark(bookmark, treeNode,DiagramEventSource.TREE);
+						uniqueBookmarkChosen = true;
+					}
+				}else{
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+					iLog("cancel add bookmark dialog","");
+					break; //user no longer wants to choose, exit the dialog thus
+				}
+			}
+		}else{ // removing a bookmark
+			String[] bookmarksArray = new String[treeNode.getBookmarkKeys().size()];
 			bookmarksArray = treeNode.getBookmarkKeys().toArray(bookmarksArray);
-			
+
 			iLog("open remove bookmark dialog","");
 			final String bookmark = (String)SpeechOptionPane.showSelectionDialog(
-					   EditorFrame.this, 
-					   resources.getString("dialog.input.bookmark.delete"),
-					   bookmarksArray,
-					   bookmarksArray[0]
-					   );
-			
+					EditorFrame.this, 
+					resources.getString("dialog.input.bookmark.delete"),
+					bookmarksArray,
+					bookmarksArray[0]
+			);
+
 			if(bookmark != null){
-				tree.getModel().removeBookmark(bookmark);
-				SoundFactory.getInstance().play(SoundEvent.OK, new PlayerListener(){
-					   @Override
-					   public void playEnded() {
-						   NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.delete.bookmark.ack"), bookmark,tree.currentPathSpeech()));
-					   }
-				   });
+				tree.getModel().removeBookmark(bookmark,DiagramEventSource.TREE);
 			}else{
 				iLog("cancel remove bookmark dialog","");
 				SoundFactory.getInstance().play(SoundEvent.CANCEL);
 			}
-	   }
-	   modelUpdater.yieldLock(treeNode, Lock.BOOKMARK);
-   }
-   
-   public void editNotes(){
-	   DiagramPanel dPanel = getActiveTab();
-	   final DiagramTree tree = dPanel.getTree();
-	   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)dPanel.getTree().getLastSelectedPathComponent();
-	   DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
-	   if(!modelUpdater.getLock(treeNode, Lock.NOTES)){
-		   iLog("Could not get lock on tree node for notes",treeNode.getName());
-		   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.notes"), SpeechOptionPane.INFORMATION_MESSAGE);
-		   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
-		   return;
-	   }
-	   
-	   String typeString = "";
-	   /* if the note is for a diagram element the dialog message is changed so that the type precedes the name */
-	   if(treeNode instanceof DiagramElement){
-		   typeString = ((DiagramElement)treeNode).getType() + " ";
-	   }
-	   /* if the note is for a property tree node the dialog message is changed so that the type precedes the name */
-	   if(treeNode instanceof PropertyMutableTreeNode){
-		   PropertyTypeMutableTreeNode parent = (PropertyTypeMutableTreeNode)treeNode.getParent();
-		   typeString = parent.getType() + " ";
-	   }
-	   iLog("open edit note dialog","");
-	   String result = SpeechOptionPane.showTextAreaDialog(EditorFrame.this, resources.getString("dialog.input.notes.text")+typeString+treeNode.getName() ,treeNode.getNotes());
-	   if(result != null){
-		   modelUpdater.setNotes(treeNode, result);
-		   SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
-			   @Override
-			   public void playEnded() {
-				   NarratorFactory.getInstance().speak(tree.currentPathSpeech());
-			   }
-		   });
-	   }else{
-		   iLog("cancel edit note dialog","");
-		   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-	   }
-	   modelUpdater.yieldLock(treeNode, Lock.NOTES);
-   }
-   
-   public void startServer(){
-	   iLog("server started","");
-	   server = Server.createServer();
-	   try{
-		   server.init(EditorFrame.this);
-	   }catch(IOException ioe){
-		   SpeechOptionPane.showMessageDialog(
-	        		 editorTabbedPane, 
-	        		 ioe.getLocalizedMessage());
-		   iLog("error: starting server",ioe.getLocalizedMessage());
-		   return;
-	   }
-	   server.start();
-	   startServer.setEnabled(false);
-	   stopServer.setEnabled(true);
-	   if(getActiveTab() != null)
-		   shareDiagramMenuItem.setEnabled(true);
-   }
-   
-   public void stopServer(){
-	   /* those network diagrams which are connected to the local server are reverted,       *
-	    * that is the diagram panel is set with the delegate diagram of the network diagram  */
-	   for(int i=0; i < editorTabbedPane.getTabCount(); i++){
-		   DiagramPanel dPanel = editorTabbedPane.getComponentAt(i);
-		   if(dPanel.getDiagram() instanceof NetDiagram){
-			   NetDiagram netDiagram = (NetDiagram)dPanel.getDiagram();
-			   if(netDiagram.getSocketChannel().equals(localSocket))
-				   dPanel.setDiagram(netDiagram.getDelegate());
-		   }
-	   }
-	   server.shutdown(resources.getString("server.shutdown_msg"));
-	   server = null;
-	   if(localSocket != null){
-		   try{localSocket.close();}catch(IOException ioe){ioe.printStackTrace();}
-		   localSocket = null;
-	   }
-	   startServer.setEnabled(true);
-	   stopServer.setEnabled(false);
-	   shareDiagramMenuItem.setEnabled(false);
-	   iLog("server stopped","");
-   }
-   
-   public void shareDiagram(){
-	   try{
-		   if(server == null)
-			   throw new DiagramShareException(resources.getString("server.not_running_exc"));
-			   
-		   DiagramPanel dPanel = getActiveTab(); 
-		   Diagram diagram = dPanel.getDiagram();
-		   try {
-			   iLog("share diagram",diagram.getName());
-			   /* check if it's already connected to the local server (a.k.a. another diagram has been shared previously */
-			   if(localSocket == null){
-				   int port = Integer.parseInt(PreferencesService.getInstance().get("server.remote_port",Server.DEFAULT_REMOTE_PORT));
-				   InetSocketAddress address = new InetSocketAddress("127.0.0.1",port);
-				   localSocket = SocketChannel.open(address);
-			   }
-			   
-			   server.share(diagram);
-			   ProtocolFactory.newInstance().send(localSocket, new Command(Command.Name.LOCAL,diagram.getName()));
-			   dPanel.setDiagram(NetDiagram.wrapLocalHost(diagram,localSocket,server.getLocalhostQueue(diagram.getName()),netLocalDiagramExceptionHandler)); 
-			   shareDiagramMenuItem.setEnabled(false);
-		   } catch (IOException e) {
-			   iLog("error sharing diagram",diagram.getName()+" "+e.getLocalizedMessage());
-			   SpeechOptionPane.showMessageDialog(EditorFrame.this, e.getLocalizedMessage());
-			   return;
-		   }
-	   }catch(DiagramShareException dse){
-		   SpeechOptionPane.showMessageDialog(EditorFrame.this, dse.getLocalizedMessage());
-	   }
-   }
-   
-   public void openSharedDiagram(){
-	   iLog("open open share diagram dialog","");
-	   /* open the window prompting for the server address and make checks on the user input */
-	   String addr = SpeechOptionPane.showInputDialog(EditorFrame.this, "Enter server address");
-	   if(addr == null){
-		   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-		   iLog("cancel open share diagram dialog","");
-		   return;
-	   }else if(!Validator.validateIPAddr(addr)){
-		   iLog("error:invalid IP address",addr);
-		   SpeechOptionPane.showMessageDialog(this, resources.getString("speech.invalid_ip"));
-		   return;
-	   }else{
-		   /* open the channel for the new diagram */
-		   SocketChannel channel = null;
-		   try {
-			   channel = SocketChannel.open();
-		   } catch (IOException e) {
-			   iLog("error:could not connect to the server","");
-			   SpeechOptionPane.showMessageDialog(EditorFrame.this, "Could not connect to server");
-			   return;
-		   }
-		   /* download the diagram list */
-		   DiagramDownloader downloader = new DiagramDownloader(
-				   channel,
-				   addr, 
-				   DiagramDownloader.Task.CONNECT_AND_DOWNLOAD_LIST
-		   );
-		   iLog("open download diagram list dialog","");
-		   int option = SpeechOptionPane.showProgressDialog(EditorFrame.this, "Downloading diagram list", downloader,500);
-		   if(option == SpeechOptionPane.CANCEL_OPTION){
-			   iLog("cancel download diagram list dialog","");
-			   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-			   try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
-		   }else{
-			   try{
-				   /* show the available diagram list */
-				   String result = downloader.get();
-				   if(result == null)
-					   throw new Exception(resources.getString("dialog.error.no_diagrams_on_server")); // go to the catch block 
-				   String[] diagramsList = result.split("\n");
-				   
-				   iLog("open select diagram to download dialog","");
-				   String diagramName = (String)SpeechOptionPane.showSelectionDialog(EditorFrame.this, "Select diagram to download", diagramsList, diagramsList[0]);
-				   if(diagramName == null){
-					   iLog("cancel select diagram to download dialog","");
-					   SoundFactory.getInstance().play(SoundEvent.CANCEL);
-					   try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
-					   return;
-				   }
-				   /* there cannot be two diagrams with the same name open at the same time */
-				   if(editorTabbedPane.getDiagramNameTabIndex(diagramName) != -1)
-					   throw new IOException(resources.getString("dialog.error.same_file_name"));
-				   /* download the chosen diagram */
-				   downloader = new DiagramDownloader(channel,diagramName,DiagramDownloader.Task.DOWNLOAD_DIAGRAM);
-				   iLog("open downloading diagram dialog",diagramName);
-				   option = SpeechOptionPane.showProgressDialog(EditorFrame.this, MessageFormat.format(resources.getString("dialog.downloading_diagram"), diagramName), downloader,500);
-				   if(option == SpeechOptionPane.CANCEL_OPTION){
-					   iLog("cancel downloading diagram dialog",diagramName);
-					   SoundFactory.getInstance().play(SoundEvent.CANCEL); 
-					   try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
-				   }else{
-					   result = downloader.get();
-					   
-					   if(clientConnectionManager == null){
+		}
+		modelUpdater.yieldLock(treeNode, Lock.BOOKMARK, DiagramEventActionSource.NULL);
+	}
+
+	public void editNotes(){
+		DiagramPanel dPanel = getActiveTab();
+		DiagramTreeNode treeNode = (DiagramTreeNode)dPanel.getTree().getLastSelectedPathComponent();
+		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
+		if(!modelUpdater.getLock(treeNode, Lock.NOTES, DiagramEventActionSource.NULL)){
+			iLog("Could not get lock on tree node for notes",treeNode.getName());
+			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.notes"), SpeechOptionPane.INFORMATION_MESSAGE);
+			SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
+			return;
+		}
+
+		String typeString = "";
+		/* if the note is for a diagram element the dialog message is changed so that the type precedes the name */
+		if(treeNode instanceof DiagramElement){
+			typeString = ((DiagramElement)treeNode).getType() + " ";
+		}
+		/* if the note is for a property tree node the dialog message is changed so that the type precedes the name */
+		if(treeNode instanceof PropertyMutableTreeNode){
+			PropertyTypeMutableTreeNode parent = (PropertyTypeMutableTreeNode)treeNode.getParent();
+			typeString = parent.getType() + " ";
+		}
+		iLog("open edit note dialog","");
+		String result = SpeechOptionPane.showTextAreaDialog(EditorFrame.this, resources.getString("dialog.input.notes.text")+typeString+treeNode.getName() ,treeNode.getNotes());
+		if(result != null){
+			modelUpdater.setNotes(treeNode, result,DiagramEventSource.TREE);
+		}else{
+			iLog("cancel edit note dialog","");
+			SoundFactory.getInstance().play(SoundEvent.CANCEL);
+		}
+		modelUpdater.yieldLock(treeNode, Lock.NOTES,DiagramEventActionSource.NULL);
+	}
+
+	public void startServer(){
+		iLog("server started","");
+		/* If the awareness filter has not been created yet (by opening the awareness filter dialog) then create it */
+		BroadcastFilter filter = BroadcastFilter.getInstance();
+		if(filter == null)
+			try{
+				filter = BroadcastFilter.createInstance();
+			}catch(IOException ioe){
+				SpeechOptionPane.showMessageDialog(this, ioe.getLocalizedMessage());
+			}
+			/* create the server */
+			server = Server.createServer();
+			try{
+				server.init();
+			}catch(IOException ioe){
+				SpeechOptionPane.showMessageDialog(
+						editorTabbedPane, 
+						ioe.getLocalizedMessage());
+				iLog("error: starting server",ioe.getLocalizedMessage());
+				return;
+			}
+			server.start();
+			startServerMenuItem.setEnabled(false);
+			stopServerMenuItem.setEnabled(true);
+			if(getActiveTab() != null && (!(getActiveTab().getDiagram() instanceof NetDiagram)))
+				shareDiagramMenuItem.setEnabled(true);
+	}
+
+	public void stopServer(){
+		/* those network diagrams which are connected to the local server are reverted,       *
+		 * that is the diagram panel is set with the delegate diagram of the network diagram  */
+		for(int i=0; i < editorTabbedPane.getTabCount(); i++){
+			DiagramPanel dPanel = editorTabbedPane.getComponentAt(i);
+			if(dPanel.getDiagram() instanceof NetDiagram){
+				NetDiagram netDiagram = (NetDiagram)dPanel.getDiagram();
+				if(netDiagram.getSocketChannel().equals(localSocket)){
+					dPanel.setAwarenessPanelEnabled(false);
+					dPanel.setDiagram(netDiagram.getDelegate());
+				}
+			}
+		}
+		server.shutdown(resources.getString("server.shutdown_msg"));
+		server = null;
+		if(localSocket != null){
+			try{localSocket.close();}catch(IOException ioe){ioe.printStackTrace();}
+			localSocket = null;
+		}
+		startServerMenuItem.setEnabled(true);
+		stopServerMenuItem.setEnabled(false);
+		shareDiagramMenuItem.setEnabled(false);
+		if(getActiveTab() != null)
+			fileCloseItem.setEnabled(true);
+		iLog("server stopped","");
+	}
+
+	public void shareDiagram(){
+		try{
+			if(server == null)
+				throw new DiagramShareException(resources.getString("server.not_running_exc"));
+
+			DiagramPanel dPanel = getActiveTab(); 
+			Diagram diagram = dPanel.getDiagram();
+			try {
+				iLog("share diagram",diagram.getName());
+				/* check if it's already connected to the local server (a.k.a. another diagram has been shared previously */
+				if(localSocket == null){
+					int port = Integer.parseInt(PreferencesService.getInstance().get("server.local_port",Server.DEFAULT_REMOTE_PORT));
+					InetSocketAddress address = new InetSocketAddress("127.0.0.1",port);
+					localSocket = SocketChannel.open(address);
+				}
+
+				server.share(diagram);
+				ProtocolFactory.newInstance().send(localSocket, new Command(Command.Name.LOCAL,diagram.getName(),DiagramEventSource.NONE));
+				dPanel.setDiagram(NetDiagram.wrapLocalHost(diagram,localSocket,server.getLocalhostQueue(diagram.getName()),netLocalDiagramExceptionHandler)); 
+				shareDiagramMenuItem.setEnabled(false);
+				fileCloseItem.setEnabled(false);
+				dPanel.setAwarenessPanelEnabled(true);
+			} catch (IOException e) {
+				iLog("error sharing diagram",diagram.getName()+" "+e.getLocalizedMessage());
+				SpeechOptionPane.showMessageDialog(EditorFrame.this, e.getLocalizedMessage());
+				return;
+			}
+		}catch(DiagramShareException dse){
+			SpeechOptionPane.showMessageDialog(EditorFrame.this, dse.getLocalizedMessage());
+		}
+	}
+
+	public void unshareDiagram(){
+		DiagramPanel dPanel = getActiveTab();
+		if(dPanel.getDiagram() instanceof NetDiagram){
+			if(clientConnectionManager != null && clientConnectionManager.isAlive())
+				clientConnectionManager.addRequest(new RmDiagramRequest(dPanel.getDiagram().getName()));
+		}
+	}
+
+	public void openSharedDiagram(){
+		iLog("open open share diagram dialog","");
+		/* open the window prompting for the server address and make checks on the user input */
+		String addr = SpeechOptionPane.showInputDialog(
+				EditorFrame.this, 
+				resources.getString("dialog.share_diagram.enter_address"),
+				PreferencesService.getInstance().get("server.address", ""));
+		if(addr == null){
+			SoundFactory.getInstance().play(SoundEvent.CANCEL);
+			iLog("cancel open share diagram dialog","");
+			return;
+		}else if(!Validator.validateIPAddr(addr)){
+			iLog("error:invalid IP address",addr);
+			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.share_diagram.wrong_ip"));
+			return;
+		}else{
+			PreferencesService.getInstance().put("server.address", addr);
+			/* open the channel for the new diagram */
+			SocketChannel channel = null;
+			try {
+				channel = SocketChannel.open();
+			} catch (IOException e) {
+				iLog("error:could not connect to the server","");
+				SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.no_connection_to_server"));
+				return;
+			}
+			/* download the diagram list */
+			DiagramDownloader downloader = new DiagramDownloader(
+					channel,
+					addr, 
+					DiagramDownloader.CONNECT_AND_DOWNLOAD_LIST_TASK
+			);
+			iLog("open download diagram list dialog","");
+			int option = SpeechOptionPane.showProgressDialog(EditorFrame.this, resources.getString("dialog.downloading_diagram_list"), downloader,500);
+			if(option == SpeechOptionPane.CANCEL_OPTION){
+				iLog("cancel download diagram list dialog","");
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
+			}else{
+				try{
+					/* show the available diagram list */
+					String result = downloader.get();
+					if(result == null)
+						throw new Exception(resources.getString("dialog.error.no_diagrams_on_server")); // go to the catch block 
+					String[] diagramsList = result.split("\n");
+
+					iLog("open select diagram to download dialog","");
+					String diagramName = (String)SpeechOptionPane.showSelectionDialog(EditorFrame.this, "Select diagram to download", diagramsList, diagramsList[0]);
+					if(diagramName == null){
+						iLog("cancel select diagram to download dialog","");
+						SoundFactory.getInstance().play(SoundEvent.CANCEL);
+						try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
+						return;
+					}
+					/* there cannot be two diagrams with the same name open at the same time */
+					if(editorTabbedPane.getDiagramNameTabIndex(diagramName) != -1)
+						throw new IOException(resources.getString("dialog.error.same_file_name"));
+					/* download the chosen diagram */
+					downloader = new DiagramDownloader(channel,diagramName,DiagramDownloader.DOWNLOAD_DIAGRAM_TASK);
+					iLog("open downloading diagram dialog",diagramName);
+					option = SpeechOptionPane.showProgressDialog(EditorFrame.this, MessageFormat.format(resources.getString("dialog.downloading_diagram"), diagramName), downloader,500);
+					if(option == SpeechOptionPane.CANCEL_OPTION){
+						iLog("cancel downloading diagram dialog",diagramName);
+						SoundFactory.getInstance().play(SoundEvent.CANCEL); 
+						try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
+					}else{
+						result = downloader.get();
+
+						if(clientConnectionManager == null || !clientConnectionManager.isAlive()){
 							clientConnectionManager = new ClientConnectionManager(editorTabbedPane);
-						    clientConnectionManager.start();
-					   }else if(!clientConnectionManager.isAlive()){
-						   clientConnectionManager = new ClientConnectionManager(editorTabbedPane);
-						   clientConnectionManager.start();
-					   }
-					   
-					   iLog("START READ NETWORK DIAGRAM "+diagramName);
-					   Diagram diagram = PersistenceManager.decodeDiagramInstance(new BufferedInputStream(new ByteArrayInputStream(result.getBytes("UTF-8"))));
-					   iLog("END READ NETWORK DIAGRAM "+diagramName);
-					   /* remove all the bookmarks in the server diagram model instance */
-					   for(String bookmarkKey : diagram.getTreeModel().getBookmarks())
-						   diagram.getTreeModel().removeBookmark(bookmarkKey);
-					   Diagram newDiagram = NetDiagram.wrapRemoteHost(diagram,clientConnectionManager,channel);
-					   addTab(null,newDiagram);
-					   clientConnectionManager.addRequest(new ClientConnectionManager.AddDiagramRequest(channel, diagram));
-				   }
-			   }catch(RuntimeException rte){
-				   throw new RuntimeException(rte);
-			   }catch(ExecutionException ee){
-				   /* if the exception happened in the DiagramDownloader then it's wrapped into an   *
-				    * ExecutionException and we have to unwrap it to get a neat message for the user */
-				   SpeechOptionPane.showMessageDialog(
-			        		 editorTabbedPane, 
-			        		 ee.getCause().getLocalizedMessage());
-				   iLog("error: "+ee.getCause().getMessage(),"");
-			       try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
-			   }catch(Exception exception){
-				   SpeechOptionPane.showMessageDialog(
-			        		 editorTabbedPane, 
-			        		 exception.getLocalizedMessage()); 
-				   iLog("error: "+exception.getMessage(),"");
-			       try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
-			   }
-		   }
-	   }
-   }
-   
-   public void backupOpenDiagrams(){
-	   SimpleDateFormat dateFormat = new SimpleDateFormat("EEE_d_MMM_yyyy_HH_mm_ss");
-	   String date = dateFormat.format(new Date());
-	   File backupDir = new File(new StringBuilder(backupDirPath)
-	   					.append(System.getProperty("file.separator"))
-	   					.append(date)
-	   					.toString());
-	   backupDir.mkdir();
-	   for(int i=0; i<editorTabbedPane.getTabCount();i++){
-		   DiagramPanel dPanel = editorTabbedPane.getComponentAt(i); 
-		   if(dPanel.isModified()||dPanel.getFilePath() == null){
-			   Diagram diagram = dPanel.getDiagram();
-			   File file = new File(backupDir,diagram.getName()+".ccmi");
-			   try {
-				   FileService.Save save = new FileService.DirectService().save((file));
-				   PersistenceManager.encodeDiagramInstance(diagram, save.getOutputStream());
-			   } catch (IOException e) {
-				   e.printStackTrace();
-			   }
-		   }
-	   }
-   }
-   
-   /**
+							clientConnectionManager.start();
+						}
+
+						iLog("START READ NETWORK DIAGRAM "+diagramName);
+						Diagram diagram = PersistenceManager.decodeDiagramInstance(new BufferedInputStream(new ByteArrayInputStream(result.getBytes("UTF-8"))));
+						iLog("END READ NETWORK DIAGRAM "+diagramName);
+						/* remove all the bookmarks in the server diagram model instance */
+						for(String bookmarkKey : diagram.getTreeModel().getBookmarks())
+							diagram.getTreeModel().removeBookmark(bookmarkKey,DiagramEventSource.TREE);
+						Diagram newDiagram = NetDiagram.wrapRemoteHost(diagram,clientConnectionManager,channel);
+						DiagramPanel dPanel = addTab(null,newDiagram);
+						/* enable awareness on the new diagram */
+						dPanel.setAwarenessPanelEnabled(true);
+						/* make the network thread aware of the new shared diagram, from here on the messages received from the server will take effect */
+						clientConnectionManager.addRequest(new ClientConnectionManager.AddDiagramRequest(channel, diagram));
+						clientConnectionManager.addRequest(new SendAwarenessRequest(channel, new AwarenessMessage(
+								AwarenessMessage.Name.USERNAME_A,
+								newDiagram.getName(),
+								AwarenessMessage.getDefaultUserName()
+						)));
+					}
+				}catch(RuntimeException rte){
+					throw new RuntimeException(rte);
+				}catch(ExecutionException ee){
+					try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
+					/* if the exception happened in the DiagramDownloader then it's wrapped into an   *
+					 * ExecutionException and we have to unwrap it to get a neat message for the user */
+					SpeechOptionPane.showMessageDialog(
+							editorTabbedPane, 
+							ee.getCause().getLocalizedMessage());
+					iLog("error: "+ee.getCause().getMessage(),"");
+				}catch(Exception exception){
+					try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
+					SpeechOptionPane.showMessageDialog(
+							editorTabbedPane, 
+							exception.getLocalizedMessage()); 
+					iLog("error: "+exception.getMessage(),"");
+				}
+			}
+		}
+	}
+
+	public void showAwarenessPanel(){
+		DiagramPanel dPanel = getActiveTab();
+		if(dPanel != null){
+			dPanel.setAwarenessPanelVisible(true);
+			NarratorFactory.getInstance().speak(resources.getString("speech.awareness_panel.open"));
+		}
+	}
+
+	public void hideAwarenessPanel(){
+		DiagramPanel dPanel = getActiveTab();
+		if(dPanel != null){
+			dPanel.setAwarenessPanelVisible(false);
+			NarratorFactory.getInstance().speak(resources.getString("speech.awareness_panel.close"));
+			dPanel.getTree().requestFocus();
+		}
+	}
+
+	public void backupOpenDiagrams(){
+		SimpleDateFormat dateFormat = new SimpleDateFormat("EEE_d_MMM_yyyy_HH_mm_ss");
+		String date = dateFormat.format(new Date());
+		File backupDir = new File(new StringBuilder(backupDirPath)
+		.append(System.getProperty("file.separator"))
+		.append(date)
+		.toString());
+		backupDir.mkdir();
+		for(int i=0; i<editorTabbedPane.getTabCount();i++){
+			DiagramPanel dPanel = editorTabbedPane.getComponentAt(i); 
+			if(dPanel.isModified()||dPanel.getFilePath() == null){
+				Diagram diagram = dPanel.getDiagram();
+				File file = new File(backupDir,diagram.getName()+".ccmi");
+				try {
+					FileService.Save save = new FileService.DirectService().save((file));
+					PersistenceManager.encodeDiagramInstance(diagram, save.getOutputStream());
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
       Exports the current graph to an image file.
-    */
-   public void exportImage(){  
-	   DiagramPanel dPanel = getActiveTab(); 
-	   if (dPanel == null) 
-		   return;
-	   OutputStream out = null;
-	   try{
-		   String imageExtensions = resources.getString("files.image.extension");
-		   /* default save dir is the same as the diagram's or home/images otherwise */
-		   String path = dPanel.getFilePath();
-		   if(path == null)
-			   path = PreferencesService.getInstance().get("dir.images", ".");
-		   FileService.Save save = fileService.save(path, dPanel.getDiagram().getName(), exportFilter, 
-				   defaultExtension, imageExtensions);
-		   out = save.getOutputStream();
-		   if (out != null){
-			   /* if the diagram has a name (has already been saved) then prompt the user with the name of 
-			    * the diagram with a jpg extension. */
-			   String fileName = FileService.getFileNameFromPath(save.getPath(),true);
-			   String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
-			   if (!ImageIO.getImageWritersByFormatName(extension).hasNext()){
-				   throw new IOException(MessageFormat.format(
-								   resources.getString("dialog.error.unsupported_image"),
-								   extension
-						   ));
-			   }
-			   GraphPanel gPanel = dPanel.getGraphPanel();
-			   try{
-				   saveImage(gPanel, out, extension);
-				   speakFocusedComponent(resources.getString("dialog.file_saved"));
-			   }catch(IOException ioe){
-				   throw new IOException(resources.getString("dialog.error.save_image"),ioe);
-			   }
-		   }
-	   }
-	   catch (IOException ioe){
-		   SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getMessage());
-	   }finally{
-		   if(out != null)
-			   try{out.close();}catch(IOException ioe){ioe.printStackTrace();}
-	   }
-   }
+	 */
+	public void exportImage(){  
+		DiagramPanel dPanel = getActiveTab(); 
+		if (dPanel == null) 
+			return;
+		OutputStream out = null;
+		try{
+			String imageExtensions = resources.getString("files.image.extension");
+			/* default save dir is the same as the diagram's or home/images otherwise */
+			String path = dPanel.getFilePath();
+			if(path == null)
+				path = PreferencesService.getInstance().get("dir.images", ".");
+			FileService.Save save = fileService.save(path, dPanel.getDiagram().getName(), exportFilter, 
+					defaultExtension, imageExtensions,null);
+			out = save.getOutputStream();
+			if (out != null){
+				/* if the diagram has a name (has already been saved) then prompt the user with the name of 
+				 * the diagram with a jpg extension. */
+				String fileName = FileService.getFileNameFromPath(save.getPath(),true);
+				String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
+				if (!ImageIO.getImageWritersByFormatName(extension).hasNext()){
+					throw new IOException(MessageFormat.format(
+							resources.getString("dialog.error.unsupported_image"),
+							extension
+					));
+				}
+				GraphPanel gPanel = dPanel.getGraphPanel();
+				try{
+					saveImage(gPanel, out, extension);
+					speakFocusedComponent(resources.getString("dialog.file_saved"));
+				}catch(IOException ioe){
+					throw new IOException(resources.getString("dialog.error.save_image"),ioe);
+				}
+			}
+		}
+		catch (IOException ioe){
+			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getMessage());
+		}finally{
+			if(out != null)
+				try{out.close();}catch(IOException ioe){ioe.printStackTrace();}
+		}
+	}
 
-   /**
+	/**
       Exports a current graph to an image file.
       @param graph the graph
       @param out the output stream
       @param format the image file format
-   */
-   public static void saveImage(GraphPanel graph, OutputStream out, String format)
-      throws IOException { 
-      // need a dummy image to get a Graphics to measure the size
-      Rectangle2D bounds = graph.getBounds();
-      BufferedImage image 
-         = new BufferedImage((int)bounds.getWidth() + 1,
-            (int)bounds.getHeight() + 1, 
-            BufferedImage.TYPE_INT_RGB);
-      Graphics2D g2 = (Graphics2D)image.getGraphics();
-      g2.translate(-bounds.getX(), -bounds.getY());
-      g2.setColor(Color.WHITE);
-      g2.fill(new Rectangle2D.Double(
-                 bounds.getX(),
-                 bounds.getY(), 
-                 bounds.getWidth() + 1,
-                 bounds.getHeight() + 1));
-      g2.setColor(Color.BLACK);
-      g2.setBackground(Color.WHITE);
-      boolean hideGrid = graph.getHideGrid();
-      graph.setHideGrid(true);
-      graph.paintComponent(g2);
-      graph.setHideGrid(hideGrid);
-   	  ImageIO.write(image, format, out);
-   }
-   
-   /**
+	 */
+	public static void saveImage(GraphPanel graph, OutputStream out, String format)
+	throws IOException { 
+		// need a dummy image to get a Graphics to measure the size
+		Rectangle2D bounds = graph.getBounds();
+		BufferedImage image 
+		= new BufferedImage((int)bounds.getWidth() + 1,
+				(int)bounds.getHeight() + 1, 
+				BufferedImage.TYPE_INT_RGB);
+		Graphics2D g2 = (Graphics2D)image.getGraphics();
+		g2.translate(-bounds.getX(), -bounds.getY());
+		g2.setColor(Color.WHITE);
+		g2.fill(new Rectangle2D.Double(
+				bounds.getX(),
+				bounds.getY(), 
+				bounds.getWidth() + 1,
+				bounds.getHeight() + 1));
+		g2.setColor(Color.BLACK);
+		g2.setBackground(Color.WHITE);
+		boolean hideGrid = graph.getHideGrid();
+		graph.setHideGrid(true);
+		graph.paintComponent(g2);
+		graph.setHideGrid(hideGrid);
+		ImageIO.write(image, format, out);
+	}
+
+	public void showAwarenessBroadcastDialog(){
+		BroadcastFilter filter = BroadcastFilter.getInstance();
+		if(filter == null)
+			try{
+				filter = BroadcastFilter.createInstance();
+			}catch(IOException ioe){
+				SpeechOptionPane.showMessageDialog(this, ioe.getLocalizedMessage());
+			}
+			filter.showDialog(this);   
+	}
+
+	public void showAwarenessDisplayDialog(){
+		DisplayFilter filter = DisplayFilter.getInstance();
+		if(filter == null)
+			try{
+				filter = DisplayFilter.createInstance();
+			}catch(IOException ioe){
+				SpeechOptionPane.showMessageDialog(this, ioe.getLocalizedMessage());
+			}
+			filter.showDialog(this); 
+	}
+
+	public void showAwarnessUsernameDialog(){
+		String oldName = AwarenessMessage.getDefaultUserName();
+		String newName = SpeechOptionPane.showInputDialog(
+				this, 
+				resources.getString("dialog.input.awerness_username"), 
+				oldName
+		);
+		if(newName == null){
+			SoundFactory.getInstance().play(SoundEvent.CANCEL);
+			return;
+		}
+
+		if(newName.trim().isEmpty()){
+			SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
+				@Override
+				public void playEnded() {
+					NarratorFactory.getInstance().speak(resources.getString("speech.empty_userame"));
+				}
+			});
+			return;
+		}
+
+		if(!newName.equals(oldName)){//if the name hasn't changed don't issue any message 
+			PreferencesService.getInstance().put("user.name", newName);
+			AwarenessMessage.setDefaultUserName(newName);
+			NarratorFactory.getInstance().speak(MessageFormat.format(
+					resources.getString("dialog.feedback.awareness_username"),
+					newName
+			));
+			for(int i=0; i<editorTabbedPane.getTabCount(); i++){
+				Diagram diagram = editorTabbedPane.getComponentAt(i).getDiagram(); 
+				diagram.getModelUpdater().sendAwarenessMessage(
+						AwarenessMessage.Name.USERNAME_A,
+						newName);// send the new name only, the old name will be added by the server 
+			}
+		}
+	}
+
+	public void showLocalServerPortDialog(){
+		showServerPortDialog("server.local_port","dialog.input.local_server_port","dialog.feedback.local_server_port");
+	}
+
+	public void showRemoteServerPortDialog(){
+		showServerPortDialog("server.remote_port","dialog.input.remote_server_port","dialog.feedback.remote_server_port");
+	}
+
+	private void showServerPortDialog(String preferenceKey,String dialogMessage,String feedbackMessage){
+		String oldPort = PreferencesService.getInstance().get(preferenceKey,Server.DEFAULT_LOCAL_PORT);
+		String newPort = SpeechOptionPane.showInputDialog(this, resources.getString(dialogMessage), oldPort);
+		if(newPort == null){
+			SoundFactory.getInstance().play(SoundEvent.CANCEL);
+			return;
+		}
+
+		boolean badFormat = false;
+		try {
+			int port = Integer.parseInt(newPort);
+			if(port <= 0 || port > 65535)
+				badFormat = true;
+		}catch(NumberFormatException nfe){
+			badFormat = true;
+		}
+
+		if(badFormat){
+			SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
+				@Override
+				public void playEnded() {
+					NarratorFactory.getInstance().speak(resources.getString("speech.bad_format_port"));
+				}
+			});
+			return;
+		}
+		PreferencesService.getInstance().put(preferenceKey,newPort);
+		NarratorFactory.getInstance().speak(MessageFormat.format(
+				resources.getString(feedbackMessage),newPort));
+	}
+
+	/**
       Displays the About dialog box.
-   */
-   public void showAboutDialog(){
-	   String options[] = {resources.getString("dialog.ok_button")};
-	   SpeechSummaryPane.showDialog(this, 
-			   resources.getString("dialog.about.title"), 
-			   MessageFormat.format(resources.getString("dialog.about"),
-					   resources.getString("app.name"),
-					   resources.getString("app.version"),
-					   resources.getString("dialog.about.description"),
-					   resources.getString("dialog.about.license")), 
-			   SpeechSummaryPane.OK_OPTION,
-			   options
-			   );
-   }
-   
-   public void showLicense() {
-	   BufferedReader reader = null; 
-	   try{
-		   reader  = new BufferedReader(
-				   new InputStreamReader(
-						   getClass().getResourceAsStream(
-								   "license.txt")));
-		   StringBuilder builder = new StringBuilder();
-		   String line;
-		   while ((line = reader.readLine()) != null){
-			   builder.append(line).append('\n');
-		   }
-		   String options[] = {resources.getString("dialog.ok_button")};
-		   SpeechSummaryPane.showDialog(editorTabbedPane, 
-				   resources.getString("dialog.license.title"), 
-				   builder.toString(), 
-				   SpeechSummaryPane.OK_OPTION,options);
-	   }catch (IOException exception){
-		   SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.license_not_found"));
-	   }finally{
-		   if(reader != null)
-			   try{reader.close();}catch(IOException ioe){ioe.printStackTrace();}
-	   }
-   }
-   
-   public void saveDiagramTemplate(Diagram diagram) throws IOException {
-			   File file = new File(
-						new StringBuilder(PreferencesService.getInstance().get("home", "."))
-						.append(System.getProperty("file.separator"))
-						.append(resources.getString("dir.templates"))
-						.append(System.getProperty("file.separator"))
-						.append(diagram.getName())
-						.append(resources.getString("template.extension"))
-						.toString()
-		 		);
-			   PersistenceManager.encodeDiagramTemplate(diagram,file);
-	   
-  }
-   
-   /**
+	 */
+	public void showAboutDialog(){
+		String options[] = {resources.getString("dialog.ok_button")};
+		SpeechSummaryPane.showDialog(this, 
+				resources.getString("dialog.about.title"), 
+				MessageFormat.format(resources.getString("dialog.about"),
+						resources.getString("app.name"),
+						resources.getString("app.version"),
+						resources.getString("dialog.about.description"),
+						resources.getString("dialog.about.license")), 
+						SpeechSummaryPane.OK_OPTION,
+						options
+		);
+	}
+
+	public void showLicense() {
+		BufferedReader reader = null; 
+		try{
+			reader  = new BufferedReader(
+					new InputStreamReader(
+							getClass().getResourceAsStream(
+									"license.txt")));
+			StringBuilder builder = new StringBuilder();
+			String line;
+			while ((line = reader.readLine()) != null){
+				builder.append(line).append('\n');
+			}
+			String options[] = {resources.getString("dialog.ok_button")};
+			SpeechSummaryPane.showDialog(editorTabbedPane, 
+					resources.getString("dialog.license.title"), 
+					builder.toString(), 
+					SpeechSummaryPane.OK_OPTION,options);
+		}catch (IOException exception){
+			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.license_not_found"));
+		}finally{
+			if(reader != null)
+				try{reader.close();}catch(IOException ioe){ioe.printStackTrace();}
+		}
+	}
+
+	public void saveDiagramTemplate(Diagram diagram) throws IOException {
+		File file = new File(
+				new StringBuilder(PreferencesService.getInstance().get("home", "."))
+				.append(System.getProperty("file.separator"))
+				.append(resources.getString("dir.templates"))
+				.append(System.getProperty("file.separator"))
+				.append(diagram.getName())
+				.append(resources.getString("template.extension"))
+				.toString()
+		);
+		PersistenceManager.encodeDiagramTemplate(diagram,file);
+
+	}
+
+	/**
    Adds a graph type to the File->New menu.
    @param resourceName the name of the menu item resource
    @param graphClass the class object for the graph
-    */
+	 */
 	public void addDiagramType(final Diagram diagram){
 		/* this is to prevent the user from creating other diagram prototypes with the same name */
 		existingTemplateNames.add(diagram.getName());
 		existingTemplates.add(diagram);
 		newMenu.add(new ResourceFactory(resources).configure(SpeechMenuFactory.getMenuItem(diagram.getName()),"",
-	      new ActionListener(){
-		     @Override
-	         public void actionPerformed(ActionEvent event){
-				   Diagram clone = (Diagram)diagram.clone();
-				   /* find a good unique name for the new tab */
-				   Pattern pattern = Pattern.compile("new "+clone.getName()+"( \\(([0-9]+)\\))?");
-				   int maxOpenDiagram = -1;
-				   for(int i=0;i<editorTabbedPane.getTabCount();i++){
-					   Matcher matcher = pattern.matcher(editorTabbedPane.getComponentAt(i).getDiagram().getName());
-				       if(matcher.matches()){
-				    	   if(matcher.group(1) == null)
-				    		   maxOpenDiagram = 0;
-				    	   else
-				    		   maxOpenDiagram = Math.max(maxOpenDiagram, Integer.parseInt(matcher.group(2)));
-				       }
-				   }
-				   if(maxOpenDiagram >= 0)
-					   clone.setName(String.format("new %s (%d)", clone.getName(),++maxOpenDiagram));
-				   else clone.setName("new "+clone.getName());
-		    	   addTab(null, clone);
-		    	   iLog("new diagram created of type: "+diagram.getName());
-	         }
-	      }));
+				new ActionListener(){
+			@Override
+			public void actionPerformed(ActionEvent event){
+				Diagram clone = (Diagram)diagram.clone();
+				/* find a good unique name for the new tab */
+				Pattern pattern = Pattern.compile("new "+clone.getName()+"( \\(([0-9]+)\\))?");
+				int maxOpenDiagram = -1;
+				for(int i=0;i<editorTabbedPane.getTabCount();i++){
+					Matcher matcher = pattern.matcher(editorTabbedPane.getComponentAt(i).getDiagram().getName());
+					if(matcher.matches()){
+						if(matcher.group(1) == null)
+							maxOpenDiagram = 0;
+						else
+							maxOpenDiagram = Math.max(maxOpenDiagram, Integer.parseInt(matcher.group(2)));
+					}
+				}
+				if(maxOpenDiagram >= 0)
+					clone.setName(String.format("new %s (%d)", clone.getName(),++maxOpenDiagram));
+				else clone.setName("new "+clone.getName());
+				addTab(null, clone);
+				iLog("new diagram created of type: "+diagram.getName());
+			}
+		}));
 	}
 
-   /**
-    * Saves the user preferences before exiting.
-    */
-   public void savePreferences(){
-      String recent = "";     
-      for (int i = 0; i < Math.min(recentFiles.size(), maxRecentFiles); i++){
-         if (recent.length() > 0) recent += "|";
-         recent += recentFiles.get(i);
-      }      
-      preferences.put("recent", recent);   
-   }
-   
-   public DiagramPanel getActiveTab(){
-	   return (DiagramPanel)editorTabbedPane.getSelectedComponent();
-   }
-   
-   public void selectHapticHighligh(DiagramElement de){
-	   hapticHighlightDiagramElement = de;
-	   highlightMenuItem.setEnabled(de == null ? false : true);
-   }
-      
-   private void addTab(String path, Diagram diagram){
-	   int newTabId = editorTabbedPane.getTabCount();
-	   DiagramPanel diagramPanel = new DiagramPanel(diagram,editorTabbedPane);
-	   diagramPanel.setFilePath(path);
-	   diagramPanel.getTree().addTreeSelectionListener(treeSelectionListener);
-	   /* update the haptics */
-	   Haptics haptics = HapticsFactory.getInstance();
-	   haptics.addNewDiagram(newTabId, true);
-	   for(Node n : diagram.getCollectionModel().getNodes())
-		   haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n));
-	   for(Edge e : diagram.getCollectionModel().getEdges()){
-		   Edge.PointRepresentation pr = e.getPointRepresentation();
-		   HapticsFactory.getInstance().addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine());
-	   }
-	   /* install the listener that manages the haptics device */
-	   diagram.getCollectionModel().addCollectionListener(hapticTrigger);
-	   
- 	   editorTabbedPane.add(diagramPanel);
- 	   editorTabbedPane.setToolTipTextAt(newTabId,path);
- 	   editorTabbedPane.setSelectedIndex(newTabId);
- 	   /* give the focus to the Content Pane, else it's grabbed by the rootPane
+	/**
+	 * Saves the user preferences before exiting.
+	 */
+	public void savePreferences(){
+		String recent = "";     
+		for (int i = 0; i < Math.min(recentFiles.size(), maxRecentFiles); i++){
+			if (recent.length() > 0) recent += "|";
+			recent += recentFiles.get(i);
+		}      
+		preferences.put("recent", recent);   
+	}
+
+	public DiagramPanel getActiveTab(){
+		return (DiagramPanel)editorTabbedPane.getSelectedComponent();
+	}
+
+	public void selectHapticHighligh(DiagramElement de){
+		hapticHighlightDiagramElement = de;
+		highlightMenuItem.setEnabled(de == null ? false : true);
+	}
+
+	private DiagramPanel addTab(String path, Diagram diagram){
+		DiagramPanel diagramPanel = new DiagramPanel(diagram,editorTabbedPane);
+		diagramPanel.setFilePath(path);
+		diagramPanel.getTree().addTreeSelectionListener(treeSelectionListener);
+		diagramPanel.setAwarenessPanelListener(awarenessPanelListener);
+		/* update the haptics */
+		haptics.addNewDiagram(diagramPanel.getDiagram().getName(), true);
+		for(Node n : diagram.getCollectionModel().getNodes())
+			haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),null);
+		for(Edge e : diagram.getCollectionModel().getEdges()){
+			Edge.PointRepresentation pr = e.getPointRepresentation();
+			HapticsFactory.getInstance().addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),null);
+		}
+		/* install the listener that handling the haptics device and the one handling the audio feedback */
+		diagram.getCollectionModel().addCollectionListener(hapticTrigger);
+		AudioFeedback audioFeedback = new AudioFeedback(diagramPanel.getTree());
+		diagram.getCollectionModel().addCollectionListener(audioFeedback);
+		diagram.getTreeModel().addDiagramTreeNodeListener(audioFeedback);
+
+		editorTabbedPane.add(diagramPanel);
+		editorTabbedPane.setToolTipTextAt(editorTabbedPane.getTabCount()-1,path);//the new panel is at tabCount -1
+		editorTabbedPane.setSelectedIndex(editorTabbedPane.getTabCount()-1);
+		/* give the focus to the Content Pane, else it's grabbed by the rootPane
  	      and it does not work when adding a new tab with the tree focused    */
- 	   getContentPane().requestFocusInWindow();
-   }
-   
-   private void diagramPanelEnabledMenuUpdate(DiagramPanel dPanel){
-	   fileSaveItem.setEnabled(false);
-	   fileSaveAsItem.setEnabled(false);
-	   fileCloseItem.setEnabled(false);
-	   shareDiagramMenuItem.setEnabled(false);
-	   graphExportItem.setEnabled(false);
-	   if(dPanel == null)
-		   return;
-	   
-	   fileSaveItem.setEnabled(true);
-	   fileSaveAsItem.setEnabled(true);
-	   graphExportItem.setEnabled(true);
-	   if(server != null && !(dPanel.getDiagram() instanceof NetDiagram))
-			   shareDiagramMenuItem.setEnabled(true);
-	   fileCloseItem.setEnabled(true);
-	   
-   }
-   
-   private void treeEnabledMenuUpdate(TreePath path){
-	   canJumpRef = false;
-	   insertMenuItem.setEnabled(false);
-	   deleteMenuItem.setEnabled(false);
-	   renameMenuItem.setEnabled(false);
-	   editNotesMenuItem.setEnabled(false);
-	   bookmarkMenuItem.setEnabled(false);
-	   jumpMenuItem.setEnabled(false);
-	   locateMenuItem.setEnabled(false);
-	   if(path == null)
-		   return;
-	   
-	   jumpMenuItem.setEnabled(true);
-	   editNotesMenuItem.setEnabled(true);
-	   bookmarkMenuItem.setEnabled(true);
-	   
-	   /* jump to reference : a reference node must be selected */
-	   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent();
+		getContentPane().requestFocusInWindow();
+		return diagramPanel;
+	}
 
-	   /* root node */
-	   if((treeNode).getParent() == null)
-		   return;
-	   
-	   if(treeNode instanceof EdgeReferenceMutableTreeNode)
-		   canJumpRef = true;
-	   
-	   if(treeNode instanceof NodeReferenceMutableTreeNode){
-		   insertMenuItem.setEnabled(true);
-		   canJumpRef  = true ;
-	   }
-	   
-	   /* insert a node : the type node must be selected */
-	   if(treeNode instanceof TypeMutableTreeNode){
-		   insertMenuItem.setEnabled(true);
-	   }
-	   
-	   /* it's a property node */
-	   if(treeNode instanceof PropertyMutableTreeNode){
-		   deleteMenuItem.setEnabled(true);
-		   renameMenuItem.setEnabled(true);
-		   insertMenuItem.setEnabled(true);
-	   }
-	   
-	   if(treeNode instanceof PropertyTypeMutableTreeNode)
-		   insertMenuItem.setEnabled(true);
-	   if(treeNode instanceof DiagramElement){
-		   deleteMenuItem.setEnabled(true);
-		   renameMenuItem.setEnabled(true);
-		   if(HapticsFactory.getInstance().isAlive())
-			   locateMenuItem.setEnabled(true);
-	   }
-   }
-   
-   private boolean readTemplateFiles(File[] files){
-	   boolean someFilesNotRead = false;
-	   for(File file : files){
+	private void diagramPanelEnabledMenuUpdate(DiagramPanel dPanel){
+		fileSaveItem.setEnabled(false);
+		fileSaveAsItem.setEnabled(false);
+		fileCloseItem.setEnabled(false);
+		shareDiagramMenuItem.setEnabled(false);
+		graphExportItem.setEnabled(false);
+		showAwarenessPanelMenuItem.setEnabled(false);
+		hideAwarenessPanelMenuItem.setEnabled(false);
+		if(dPanel == null)
+			return;
+
+		fileSaveItem.setEnabled(true);
+		fileSaveAsItem.setEnabled(true);
+		graphExportItem.setEnabled(true);
+		if(dPanel.getDiagram() instanceof NetDiagram){
+			if(dPanel.isAwarenessPanelVisible()) 
+				hideAwarenessPanelMenuItem.setEnabled(true);
+			else
+				showAwarenessPanelMenuItem.setEnabled(true);
+		}
+
+		boolean isSharedDiagram = dPanel.getDiagram() instanceof NetDiagram; 
+		if(server != null && !isSharedDiagram){
+			shareDiagramMenuItem.setEnabled(true);
+		}
+		
+		if(!(isSharedDiagram && dPanel.getDiagram().getLabel().endsWith(NetDiagram.LOCALHOST_STRING)))
+			fileCloseItem.setEnabled(true);
+	}
+
+	private void treeEnabledMenuUpdate(TreePath path){
+		canJumpRef = false;
+		insertMenuItem.setEnabled(false);
+		deleteMenuItem.setEnabled(false);
+		renameMenuItem.setEnabled(false);
+		editNotesMenuItem.setEnabled(false);
+		bookmarkMenuItem.setEnabled(false);
+		jumpMenuItem.setEnabled(false);
+		locateMenuItem.setEnabled(false);
+		selectMenuItem.setEnabled(false);
+		if(path == null)
+			return;
+
+		jumpMenuItem.setEnabled(true);
+		editNotesMenuItem.setEnabled(true);
+		bookmarkMenuItem.setEnabled(true);
+
+		/* jump to reference : a reference node must be selected */
+		DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();
+
+		/* root node */
+		if((treeNode).getParent() == null)
+			return;
+
+		if(treeNode instanceof EdgeReferenceMutableTreeNode)
+			canJumpRef = true;
+
+		if(treeNode instanceof NodeReferenceMutableTreeNode){
+			insertMenuItem.setEnabled(true);
+			canJumpRef  = true ;
+		}
+
+		/* insert a node : the type node must be selected */
+		if(treeNode instanceof TypeMutableTreeNode){
+			insertMenuItem.setEnabled(true);
+		}
+
+		/* it's a property node */
+		if(treeNode instanceof PropertyMutableTreeNode){
+			deleteMenuItem.setEnabled(true);
+			renameMenuItem.setEnabled(true);
+			insertMenuItem.setEnabled(true);
+		}
+
+		if(treeNode instanceof PropertyTypeMutableTreeNode)
+			insertMenuItem.setEnabled(true);
+		if(treeNode instanceof DiagramElement){
+			deleteMenuItem.setEnabled(true);
+			renameMenuItem.setEnabled(true);
+			if(HapticsFactory.getInstance().isAlive())
+				locateMenuItem.setEnabled(true);
+			if(treeNode instanceof Node)
+				selectMenuItem.setEnabled(true);
+		}
+	}
+
+	private boolean readTemplateFiles(File[] files){
+		boolean someFilesNotRead = false;
+		for(File file : files){
 			try {
 				Diagram d = PersistenceManager.decodeDiagramTemplate(file);
 				addDiagramType(d);
@@ -1893,61 +2116,65 @@
 				e.printStackTrace();
 			}
 		}
-	   return someFilesNotRead;
-   }
-   
-   private void iLog(String action,String args){
-	   InteractionLog.log("TREE",action,args);
-   }
-   
-   private void iLog(String message){
-	   InteractionLog.log(message);
-   }
+		return someFilesNotRead;
+	}
 
-   private Server server;
-   private SocketChannel localSocket;
-   private ExceptionHandler netLocalDiagramExceptionHandler;
-   private ClientConnectionManager clientConnectionManager;
-   private Haptics haptics;
-   private ResourceBundle resources;
-   private EditorTabbedPane editorTabbedPane;
-   private FileService.ChooserService fileService;
-   private PreferencesService preferences;
-   private HapticTrigger hapticTrigger;
-   private DiagramElement hapticHighlightDiagramElement;
-   private TemplateEditor[] templateEditors;
-   private ArrayList<String> existingTemplateNames;
-   private ArrayList<Diagram> existingTemplates;
-   
-   private JMenu newMenu;
-   private JMenuItem jumpMenuItem;
-   private boolean canJumpRef;
-   private JMenuItem fileSaveItem;
-   private JMenuItem graphExportItem;
-   private JMenuItem fileSaveAsItem;
-   private JMenuItem fileCloseItem;
-   private JMenuItem insertMenuItem;
-   private JMenuItem deleteMenuItem;
-   private JMenuItem renameMenuItem;
-   private JMenuItem bookmarkMenuItem;
-   private JMenuItem editNotesMenuItem;
-   private JMenuItem locateMenuItem;
-   private JMenuItem highlightMenuItem;
-   private JMenuItem shareDiagramMenuItem;
-   private JMenuItem startServer; 
-   private JMenuItem stopServer;
-   private TreeSelectionListener treeSelectionListener;
-   private ChangeListener tabChangeListener; 
-   private String defaultExtension;
-   private String backupDirPath;
-   private ArrayList<String> recentFiles;
-   private JMenu recentFilesMenu;
-   private int maxRecentFiles = DEFAULT_MAX_RECENT_FILES;
+	private void iLog(String action,String args){
+		InteractionLog.log("TREE",action,args);
+	}
 
-   private ExtensionFilter extensionFilter;
-   private ExtensionFilter exportFilter;
+	private void iLog(String message){
+		InteractionLog.log(message);
+	}
 
-   private static final int DEFAULT_MAX_RECENT_FILES = 5;
-   private static final double GROW_SCALE_FACTOR = Math.sqrt(2);
-  
+	private Server server;
+	private SocketChannel localSocket;
+	private ExceptionHandler netLocalDiagramExceptionHandler;
+	private ClientConnectionManager clientConnectionManager;
+	private Haptics haptics;
+	private ResourceBundle resources;
+	private EditorTabbedPane editorTabbedPane;
+	private FileService.ChooserService fileService;
+	private PreferencesService preferences;
+	private HapticTrigger hapticTrigger;
+	private DiagramElement hapticHighlightDiagramElement;
+	private TemplateEditor[] templateEditors;
+	private ArrayList<String> existingTemplateNames;
+	private ArrayList<Diagram> existingTemplates;
+	private AwarenessPanelEnablingListener awarenessPanelListener;
+
+	private JMenu newMenu;
+	private JMenuItem jumpMenuItem;
+	private boolean canJumpRef;
+	private JMenuItem fileSaveItem;
+	private JMenuItem graphExportItem;
+	private JMenuItem fileSaveAsItem;
+	private JMenuItem fileCloseItem;
+	private JMenuItem insertMenuItem;
+	private JMenuItem deleteMenuItem;
+	private JMenuItem renameMenuItem;
+	private JMenuItem selectMenuItem;
+	private JMenuItem bookmarkMenuItem;
+	private JMenuItem editNotesMenuItem;
+	private JMenuItem locateMenuItem;
+	private JMenuItem highlightMenuItem;
+	private JMenuItem shareDiagramMenuItem;
+	private JMenuItem startServerMenuItem; 
+	private JMenuItem stopServerMenuItem;
+	private JMenuItem showAwarenessPanelMenuItem;
+	private JMenuItem hideAwarenessPanelMenuItem;
+	private TreeSelectionListener treeSelectionListener;
+	private ChangeListener tabChangeListener; 
+	private String defaultExtension;
+	private String backupDirPath;
+	private ArrayList<String> recentFiles;
+	private JMenu recentFilesMenu;
+	private int maxRecentFiles = DEFAULT_MAX_RECENT_FILES;
+
+	private ExtensionFilter extensionFilter;
+	private ExtensionFilter exportFilter;
+
+	private static final int DEFAULT_MAX_RECENT_FILES = 5;
+	private static final double GROW_SCALE_FACTOR = Math.sqrt(2);
+
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties	Wed Apr 25 17:09:09 2012 +0100
@@ -1,7 +1,6 @@
 ### APPLICATION ###
 app.name=CCmI Editor
-app.copyright=Copyright (C) 2002...2005 Cay S. Horstmann (http://horstmann.com)
-app.version=0.1a
+app.version=0.1.4
 files.name=CCmI Diagram files
 files.extension=.ccmi
 template.extension=.xml
@@ -10,7 +9,6 @@
 
 ### EDITOR ### 
 
-error.version=You need Java version {0}
 files.image.name=Image Files
 files.image.extension=.jpg
 grabber.text=Select
@@ -18,6 +16,8 @@
 window.focus=Editor window focused
 window.unfocus=Editor window unfocused
 window.tab=Tab, {0}
+window.tree=Tree
+window.no_tabs=No diagrams open
 
 #### DIALOGS ###
 dialog.about={0} version {1}\u000A\u000A{2}\u000A\u000A{3}
@@ -43,16 +43,18 @@
 dialog.error.local_server=Error: Problems in communication with local server
 dialog.error.save_template=Error: could not save template
 dialog.error.no_template_to_edit=Error: there are no template to edit
-dialog.error.same_file_name=Cannot open two diagrams with the same name
+dialog.error.same_file_name=Cannot have two diagrams with the same name open at the same time
 dialog.error.no_diagrams_on_server=No diagrams available on the server
 dialog.error.file_exists=File already exists
 dialog.error.license_not_found=Could not retrieve the license
 dialog.error.save_image=Error: could not save the image to file
 dialog.error.unsupported_image=Error: {0} not supported
+dialog.error.no_connection_to_server=Could not connect to the server
 
 dialog.template_created=Diagram {0} created
 dialog.file_saved=File Saved
 dialog.downloading_diagram=Downloading diagram: {0}
+dialog.downloading_diagram_list=Downloading diagram list
 
 dialog.warning.title=Warning
 dialog.warning.null_modifiers=Nothing to edit for {0}
@@ -72,8 +74,8 @@
 dialog.input.property.text=New {0}, enter name  
 dialog.input.rename=Renaming {0}, Enter new name.
 dialog.input.jump.select=Where would you like to jump to ?
-dialog.input.edge_operation.label=Add label
-dialog.input.edge_operation.arrow_head=Add Arrow Head
+dialog.input.edge_operation.label=Set label
+dialog.input.edge_operation.arrow_head=Set Arrow Head
 dialog.input.edge_operation.title= Edge End Operation
 dialog.input.edge_operation.select=What would you like to do ?
 dialog.input.edge_label=Add label to {0} {1}, enter name
@@ -82,6 +84,9 @@
 dialog.input.edge_arrowhead=Select arrow head for {0} {1}
 dialog.input.sound_rate=Select rate value
 dialog.input.edit_diagram_template=Select Diagram to edit
+dialog.input.awerness_username=Enter Awareness User Name 
+dialog.input.local_server_port=Enter local server port number
+dialog.input.remote_server_port=Enter remote server port number
 
 dialog.confirm.deletion=Are you sure you want to delete the {0} {1} ?
 dialog.confirm.deletions=Are you sure you want to delete the selected objects ?
@@ -115,10 +120,16 @@
 dialog.speech_option_pane.message= {0}. Press OK to confirm
 dialog.speech_option_pane.cancel=Cancel
 
+dialog.share_diagram.wrong_ip=invalid IP address
+dialog.share_diagram.enter_address="Enter server address"
+
 dialog.file_chooser.file_type=File Type:
 dialog.file_chooser.file_name=file Name:
 
-dialog.speech_rate.message=Speech rate set to {0} 
+dialog.feedback.speech_rate=Speech rate set to {0} 
+dialog.feedback.awareness_username=User Name set to {0}
+dialog.feedback.local_server_port=Local server port number set to {0}
+dialog.feedback.remote_server_port=Remote server port number set to {0}
 
 server.shutdown_msg=request by user
 server.not_running_exc=Server not running
@@ -136,6 +147,7 @@
 speech.note.updated=Note updated 
 speech.empty_property=Empty string, nothing created. {0}
 speech.empty_label=Empty label
+speech.empty_userame=User Name cannot be empty
 speech.selected=selected
 speech.unselected=unselected
 speech.input.property.ack={0} created, 
@@ -143,17 +155,33 @@
 speech.input.edge.ack=, connected, 
 speech.input.edge.ack2= and 
 speech.invalid_ip=invalid IP address
+speech.haptic_device_crashed=Haptic device crashed. Unsaved diagrams saved in backup directory
+speech.bad_format_port=Bad port number format
 
 speech.diagram_closed= {0} closed.
 speech.node_selected={0} selected
 speech.node_unselected={0} unselected
 speech.jump=Jump to {0}
 
+speech.awareness_panel.open=Awareness panel open
+speech.awareness_panel.close=Awareness panel close
+
 ### TABBED PANE ### 
 tab.new_tab=new {0}
 tab.new_tab_id=new {0} ({1})
 
 ### MENU ### 
+
+menufactory.selected={0} selected
+menufactory.unselected={0} unselected
+menufactory.3dot= dot dot dot
+menufactory.disabled=, disabled
+menufactory.ctrl=, control
+menufactory.menu=menu
+menufactory.submenu=sub menu
+menufactory.leaving=Leaving Menu
+
+
 file.text=File
 file.mnemonic=F
 file.new.text=New Diagram
@@ -192,6 +220,7 @@
 edit.rename.text=Rename
 edit.rename.mnemonic=R
 edit.rename.accelerator=ctrl R
+edit.select.text=Select
 edit.bookmark.text=Add/Remove Bookmark
 edit.bookmark.mnemonic=B
 edit.bookmark.accelerator=ctrl B
@@ -213,13 +242,6 @@
 edit.highlight.text=Highlight
 edit.highlight.mnemonics=H
 edit.highlight.accelerator=ctrl H
-sound.text=Sound
-sound.mnemonic=S
-sound.mute.text=Mute
-sound.mute.mnemonic=M
-sound.mute.accelerator=ctrl M
-sound.rate.text=Set Speech Rate
-sound.rate.mnemonic=R
 view.text=View
 view.mnemonic=V
 view.zoom_out.text=Zoom out
@@ -248,6 +270,30 @@
 collab.stop_server.text=Stop Server
 collab.share_diagram.text=Share Diagram
 collab.open_shared_diagram.text= Open Shared Diagram
+collab.unshare_diagram.text=Stop Diagram Sharing
+collab.show_awareness_panel.text=Show Awareness Panel
+collab.hide_awareness_panel.text=Hide Awareness Panel
+preferences.text=Preferences
+preferences.mnemonic=P
+preferences.awareness.text=Awareness
+preferences.awareness.mnemonic=A
+preferences.awareness.username.text=User Name
+preferences.awareness.broadcast.text=Broadcast Filter
+preferences.awareness.display.text=Display Filter
+preferences.awareness.enable_voice.text=Enable Awareness Voice
+preferences.file_chooser.text=Enable accessible file chooser
+preferences.sound.text=Sound
+preferences.sound.mnemonic=S
+preferences.sound.mute.text=Mute
+preferences.sound.mute.mnemonic=M
+preferences.sound.mute.accelerator=ctrl M
+preferences.sound.rate.text=Set Speech Rate
+preferences.sound.rate.mnemonic=R
+preferences.server.text=Networking
+preferences.local_server.port.text=Local Server Port
+preferences.remote_server.port.text=Remote Server Port
+
+
 help.text=Help
 help.mnemonic=H
 help.about.text=About
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorTabbedPane.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorTabbedPane.java	Wed Apr 25 17:09:09 2012 +0100
@@ -18,67 +18,36 @@
 */
 package uk.ac.qmul.eecs.ccmi.gui;
 
-import java.awt.AWTKeyStroke;
 import java.awt.Component;
-import java.awt.FocusTraversalPolicy;
-import java.awt.KeyboardFocusManager;
 import java.awt.event.ActionEvent;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 
 import javax.swing.AbstractAction;
-import javax.swing.JComponent;
-import javax.swing.JFrame;
 import javax.swing.JTabbedPane;
-import javax.swing.JTree;
 import javax.swing.KeyStroke;
 
 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
-import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
+import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
 
 /**
  * 
- * The tabbed pane of the editor. On each tab a {@link DiagramPanel} is displayed. 
+ * The tabbed pane of the editor. On each tab a {@code DiagramPanel} is displayed. 
  *
  */
 @SuppressWarnings("serial")
 public class EditorTabbedPane extends JTabbedPane {
-	public EditorTabbedPane(JFrame frame){
-		this.frame = frame;
+	/**
+	 * Creates a new {@code EditorTabbedPane}
+	 * 
+	 * @param frame the frame when this tabbed pane will be placed
+	 */
+	public EditorTabbedPane(EditorFrame frame){
 		setFocusTraversalKeysEnabled(false);
-		/* get the look and feel default keys for moving the focus on (usually = TAB) */
-		for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS))
-			getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"tab");
-
-		/* add action to the moving focus keys: reproduce focus system and add speech to it */
-		getActionMap().put("tab", new AbstractAction(){
-			@Override
-			public void actionPerformed(ActionEvent evt) {
-				FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
-				Component next = policy.getComponentAfter(EditorTabbedPane.this.frame, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
-				next.requestFocusInWindow();
-				String componentType = (next instanceof JTree)  ? "tree "  : "tab " ;
-				NarratorFactory.getInstance().speak(componentType + next.getAccessibleContext().getAccessibleName());
-				InteractionLog.log("TABBED PANE","change focus to "+componentType,next.getAccessibleContext().getAccessibleName());
-			}		
-		});
-
-		/* same thing with the back tab */
-		for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS))
-			getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"back_tab");
-
-		getActionMap().put("back_tab", new AbstractAction(){
-			@Override
-			public void actionPerformed(ActionEvent evt) {
-				FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
-				Component previous = policy.getComponentBefore(EditorTabbedPane.this.frame, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
-				previous.requestFocusInWindow();
-				String componentType = (previous instanceof JTree)  ? "tree "  : "tab " ;
-				NarratorFactory.getInstance().speak(componentType+previous.getAccessibleContext().getAccessibleName());
-				InteractionLog.log("TABBED PANE","change focus to "+componentType,previous.getAccessibleContext().getAccessibleName());
-			}
-		});
 		
+		SpeechUtilities.changeTabListener(this,frame);
+		getAccessibleContext().setAccessibleName("tab ");
+				
 		/* shut up the narrator upon pressing ctrl */
 		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown");
 		getActionMap().put("ctrldown",new AbstractAction(){
@@ -88,6 +57,12 @@
 		});
 	}
 
+	/**
+	 * Sets the title of the tab containing a component.
+	 * 
+	 * @param component the component in the tab whose title has to be set
+	 * @param title the new title
+	 */
 	public void setComponentTabTitle(Component component, String title){
 		int index = indexOfComponent(component); 
 		if(index == -1)
@@ -95,6 +70,12 @@
 		setTitleAt(index,title);
 	}
 	
+	/**
+	 * Returns the title of the tab containing a component. 
+	 * 
+	 * @param component the component contained by the tab
+	 * @return the title of the tab containing {@code component}
+	 */
 	public String getComponentTabTitle(Component component){
 		int index = indexOfComponent(component);
 		if(index == -1)
@@ -102,6 +83,11 @@
 		return getTitleAt(index);
 	}
 	
+	/**
+	 * Repaints the title on a tab containing a component.
+ 	 * 
+	 * @param component the component contained by the tab whose title will be repainted
+	 */
 	public void refreshComponentTabTitle(Component component){
 		setComponentTabTitle(component,component.getName());
 	}
@@ -110,7 +96,16 @@
 	public DiagramPanel getComponentAt(int n){
 		return (DiagramPanel)super.getComponent(n);
 	}
-	
+
+	/**
+	 * The components in an {@code EditorTabbedPane} are all instances of {@code DiagramPanel}, which in turns
+	 * holds an instance of {@code Diagram}. This utility methods retrieves the index of 
+	 * the {@code DiagramPanel} whose diagram has the same name than the {@code String} passed as argument. 
+	 * 
+	 * @param diagramName the name of the diagram to look for 
+	 * @return the index of the diagram named as {@code diagramName} or {@code -1} if
+	 * such diagram doesn't exist
+	 */
 	public int getDiagramNameTabIndex(String diagramName){
 		for(int i=0; i<getTabCount();i++){
 			DiagramPanel dPanel = getComponentAt(i); 
@@ -121,6 +116,15 @@
 		return -1;
 	}
 	
+	/**
+	 * The components in an {@code EditorTabbedPane} are all instances of {@code DiagramPanel}, which in turns
+	 * holds an instance of {@code Diagram}. This method returns the index of the {@code DiagramPanel}
+	 * whose diagram has been saved on the file system at the path specified as argument. 
+	 * 
+	 * @param path
+	 * @return the index of the {@code DiagramPanel} whose diagram has been saved on 
+	 * the file system in {@code path}, or {@code -1} if such diagram doesn't exist
+	 */
 	public int getPathTabIndex(String path){
 		for(int i=0; i<getTabCount();i++){
 			DiagramPanel dPanel = getComponentAt(i); 
@@ -130,7 +134,4 @@
 		}
 		return -1;
 	}
-	
-	private JFrame frame;
-
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java	Wed Apr 25 17:09:09 2012 +0100
@@ -1,3 +1,21 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
 package uk.ac.qmul.eecs.ccmi.gui;
 
 import java.awt.Frame;
@@ -24,193 +42,215 @@
 public abstract class FileService
 {
 
-   /**
-    * An Open object encapsulates the stream, name and path of the file that the user selected for opening.
-    */
-   public interface Open
-   {
-      /**
-       * Gets the input stream corresponding to the user selection.
-       * @return the input stream, or null if the user cancels the file selection task   
-       */
-      InputStream getInputStream() throws IOException ;
-      /**
-       * Gets the name of the file that the user selected.
-       * @return the file name, or null if the user cancels the file selection task    
-       */
-      String getName() throws IOException ;
-      
-      /**
-       * Gets the path of the file that the user selected.
-       * @return the file path , or null if the user cancels the file selection task     
-       */
-      String getPath() throws IOException;
-      
-   }
+	/**
+	 * An Open object encapsulates the stream, name and path of the file that the user selected for opening.
+	 */
+	public interface Open
+	{
+		/**
+		 * Gets the input stream corresponding to the user selection.
+		 * @return the input stream, or null if the user cancels the file selection task   
+		 */
+		InputStream getInputStream() throws IOException ;
+		/**
+		 * Gets the name of the file that the user selected.
+		 * @return the file name, or null if the user cancels the file selection task    
+		 */
+		String getName() throws IOException ;
 
-   /**
-    * A Save object encapsulates the stream and name of the file that the user selected for saving.
-    */
-   public interface Save
-   {
-      /**
-       * Gets the output stream corresponding to the user selection.
-       * @return the output stream, or null if the user cancels the file selection task     
-       */
-      OutputStream getOutputStream();
-      /**
-       * Gets the name of the file that the user selected.
-       * @return the file name, or null if the user cancels the file selection task 
-       */
-      String getName();
-      /**
-       * Gets the path of the file that the user selected.
-       * @return the file path, or null if the user cancels the file selection task
-       */
-      String getPath();
-   }
+		/**
+		 * Gets the path of the file that the user selected.
+		 * @return the file path , or null if the user cancels the file selection task     
+		 */
+		String getPath() throws IOException;
 
-   /**
-    * This class implements a FileService with a JFileChooser
-    */
-   public static class ChooserService 
-   {
-      public ChooserService(File initialDirectory){
-    	 useAccessible = Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true")); 
-         fileChooser = FileChooserFactory.getFileChooser(useAccessible);
-         fileChooser.setCurrentDirectory(initialDirectory);
-      }
+	}
 
-      /* If the user cancels the task (presses cancel button or the X at the top left)    *
-       * the CANCEl sound is played (together with the registered playerListeenr if any)  */
-      public FileService.Open open(String defaultDirectory, String defaultFile, 
-         ExtensionFilter filter, Frame frame) throws FileNotFoundException {
-         fileChooser.resetChoosableFileFilters();
-         fileChooser.setFileFilter(filter);
-         if (defaultDirectory != null)
-            fileChooser.setCurrentDirectory(new File(defaultDirectory));
-         if (defaultFile == null)             
-            fileChooser.setSelectedFile(null);
-         else
-            fileChooser.setSelectedFile(new File(defaultFile));   
-         int response = fileChooser.showOpenDialog(frame);         
-         if (response == JFileChooser.APPROVE_OPTION)
-            return new OpenImpl(fileChooser.getSelectedFile());
-         else{
-        	if(useAccessible)
-        		SoundFactory.getInstance().play(SoundEvent.CANCEL);
-            return new OpenImpl(null);
-         }
-      }
-  
+	/**
+	 * A Save object encapsulates the stream and name of the file that the user selected for saving.
+	 */
+	public interface Save
+	{
+		/**
+		 * Gets the output stream corresponding to the user selection.
+		 * @return the output stream, or null if the user cancels the file selection task     
+		 */
+		OutputStream getOutputStream();
+		/**
+		 * Gets the name of the file that the user selected.
+		 * @return the file name, or null if the user cancels the file selection task 
+		 */
+		String getName();
+		/**
+		 * Gets the path of the file that the user selected.
+		 * @return the file path, or null if the user cancels the file selection task
+		 */
+		String getPath();
+	}
 
-      /* If the user cancels the task (presses cancel button or the X at the top left)   *
-       * the CANCEl sound is played (together with the registered playerListeenr if any) */
-      public FileService.Save save(String defaultDirectory, String defaultFile, 
-         ExtensionFilter filter, String removeExtension, String addExtension) throws FileNotFoundException {
-         fileChooser.resetChoosableFileFilters();
-         fileChooser.setFileFilter(filter);
-         if (defaultDirectory == null)
-            fileChooser.setCurrentDirectory(new File("."));
-         else
-            fileChooser.setCurrentDirectory(new File(defaultDirectory));
-         if (defaultFile != null){
-            File f = new File(editExtension(defaultFile, removeExtension, addExtension));                  
-            if(f.exists())
-            	fileChooser.setSelectedFile(f);
-            else
-            	fileChooser.setSelectedFile(null);
-         }else 
-            fileChooser.setSelectedFile(null);
-         int response = fileChooser.showSaveDialog(null);         
-         if (response == JFileChooser.APPROVE_OPTION){
-            File f = fileChooser.getSelectedFile();
-            if (addExtension != null && f.getName().indexOf(".") < 0) // no extension supplied
-               f = new File(f.getPath() + addExtension);
-            if (!f.exists()) return new SaveImpl(f);
-            
-            /* file with this name already exists, we must ask the user to confirm */
-            ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
-            if(useAccessible){
-            	int result = SpeechOptionPane.showConfirmDialog(
-  	                  null,
-  	                  resources.getString("dialog.overwrite"), 
-  	                  SpeechOptionPane.YES_NO_OPTION);
-  	            if (result == SpeechOptionPane.YES_OPTION) 
-  	               return new SaveImpl(f);
-            }else{
-	            int result = JOptionPane.showConfirmDialog(
-	                  null,
-	                  resources.getString("dialog.overwrite"), 
-	                  null,
-	                  JOptionPane.YES_NO_OPTION);
-	            if (result == JOptionPane.YES_OPTION) 
-	               return new SaveImpl(f);
-            }
-         }
-         if(useAccessible)
-        	 SoundFactory.getInstance().play(SoundEvent.CANCEL);
-         return new SaveImpl(null);
-      }
-      
-      private FileChooser fileChooser;
-      private boolean useAccessible;
-   }
-   
-   public static class DirectService {
-	   public Open open(File file) throws IOException{
-		   return new OpenImpl(file);
-	   }
-	   
-	   public Save save(File file) throws IOException{
-		   return new SaveImpl(file);
-	   }
-   }
+	/**
+	 * This class implements a FileService with a JFileChooser
+	 */
+	public static class ChooserService 
+	{
+		public ChooserService(File initialDirectory){
+			useAccessible = Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true")); 
+			fileChooser = FileChooserFactory.getFileChooser(useAccessible);
+			fileChooser.setCurrentDirectory(initialDirectory);
+		}
 
-   private static class SaveImpl implements FileService.Save{
-	   public SaveImpl(File f) throws FileNotFoundException{
-		   if (f != null){
-			   path = f.getPath();
-			   name = getFileNameFromPath(path,false); 
-			   out = new BufferedOutputStream(new FileOutputStream(f));
-		   }
-	   }
-	   
-	   @Override
-	   public String getName() { return name; }
-	   @Override
-	   public String getPath() {return path; }
-	   @Override
-	   public OutputStream getOutputStream() { return out; }
-	   
-	   private String name;
-	   private String path;
-	   private OutputStream out;
-   }
-   
-   private static class OpenImpl implements FileService.Open
-   {
-	   public OpenImpl(File f) throws FileNotFoundException{
-		   if (f != null){
-			   path = f.getPath();
-			   name = getFileNameFromPath(path,false);
-			   in = new BufferedInputStream(new FileInputStream(f));
-		   }
-	   }
-	   
-	   @Override
-	   public String getName() { return name; }
-	   @Override
-	   public String getPath() { return path; }
-	   @Override
-	   public InputStream getInputStream() { return in; }
-	   
-	   private String path;
-	   private String name;
-	   private InputStream in;
-   }
-   
-   /**
+		public FileService.Open open(String defaultDirectory, String defaultFile, 
+				ExtensionFilter filter, Frame frame) throws FileNotFoundException {
+			checkChangedOption();
+			fileChooser.resetChoosableFileFilters();
+			fileChooser.setFileFilter(filter);
+			if (defaultDirectory != null)
+				fileChooser.setCurrentDirectory(new File(defaultDirectory));
+			if (defaultFile == null)             
+				fileChooser.setSelectedFile(null);
+			else
+				fileChooser.setSelectedFile(new File(defaultFile));       
+			int response = fileChooser.showOpenDialog(frame);         
+			if (response == JFileChooser.APPROVE_OPTION)
+				return new OpenImpl(fileChooser.getSelectedFile());
+			else{
+				/* If the user cancels the task (presses cancel button or the X at the top left corner) *
+				 * the CANCEl sound is played (together with the registered playerListeenr if any)      */
+				if(useAccessible)
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				return new OpenImpl(null);
+			}
+		}
+
+
+		/* If the user cancels the task (presses cancel button or the X at the top left)   *
+		 * the CANCEl sound is played (together with the registered playerListeenr if any) */
+		public FileService.Save save(String defaultDirectory, String defaultFile, 
+				ExtensionFilter filter, String removeExtension, String addExtension, String[] currentTabs) throws IOException {
+			checkChangedOption();
+			fileChooser.resetChoosableFileFilters();
+			fileChooser.setFileFilter(filter);
+			if (defaultDirectory == null)
+				fileChooser.setCurrentDirectory(new File("."));
+			else
+				fileChooser.setCurrentDirectory(new File(defaultDirectory));
+			if (defaultFile != null){
+				File f = new File(editExtension(defaultFile, removeExtension, addExtension));                  
+				if(f.exists())
+					fileChooser.setSelectedFile(f);
+				else
+					fileChooser.setSelectedFile(null);
+			}else 
+				fileChooser.setSelectedFile(null);
+			int response = fileChooser.showSaveDialog(null);         
+			if (response == JFileChooser.APPROVE_OPTION){
+				ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+				File f = fileChooser.getSelectedFile();
+				if (addExtension != null && f.getName().indexOf(".") < 0) // no extension supplied
+					f = new File(f.getPath() + addExtension);
+				
+				String fileName = getFileNameFromPath(f.getAbsolutePath(),false);
+				for(String tab : currentTabs){
+					if(fileName.equals(tab))
+						throw new IOException(resources.getString("dialog.error.same_file_name"));
+				}
+				
+				if (!f.exists()) // file doesn't exits return the new SaveImpl with no problems
+					return new SaveImpl(f);
+
+				/* file with this name already exists, we must ask the user to confirm */
+				if(useAccessible){
+					int result = SpeechOptionPane.showConfirmDialog(
+							null,
+							resources.getString("dialog.overwrite"), 
+							SpeechOptionPane.YES_NO_OPTION);
+					if (result == SpeechOptionPane.YES_OPTION) 
+						return new SaveImpl(f);
+				}else{
+					int result = JOptionPane.showConfirmDialog(
+							null,
+							resources.getString("dialog.overwrite"), 
+							null,
+							JOptionPane.YES_NO_OPTION);
+					if (result == JOptionPane.YES_OPTION) 
+						return new SaveImpl(f);
+				}
+			}
+			if(useAccessible)
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+			/* returned if the user doesn't want to overwrite the file */ 
+			return new SaveImpl(null);
+		}
+
+		/* check if the user has changed the configuration since the last time a the fileChooser was shown */ 
+		private void checkChangedOption(){
+			boolean useAccessible = Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true"));
+			if(this.useAccessible != useAccessible){
+				this.useAccessible = useAccessible;
+				File currentDir = fileChooser.getCurrentDirectory();
+				fileChooser = FileChooserFactory.getFileChooser(useAccessible);
+				fileChooser.setCurrentDirectory(currentDir);
+			}
+		}
+
+		private FileChooser fileChooser;
+		private boolean useAccessible;
+	}
+
+	public static class DirectService {
+		public Open open(File file) throws IOException{
+			return new OpenImpl(file);
+		}
+
+		public Save save(File file) throws IOException{
+			return new SaveImpl(file);
+		}
+	}
+
+	private static class SaveImpl implements FileService.Save{
+		public SaveImpl(File f) throws FileNotFoundException{
+			if (f != null){
+				path = f.getPath();
+				name = getFileNameFromPath(path,false); 
+				out = new BufferedOutputStream(new FileOutputStream(f));
+			}
+		}
+
+		@Override
+		public String getName() { return name; }
+		@Override
+		public String getPath() {return path; }
+		@Override
+		public OutputStream getOutputStream() { return out; }
+
+		private String name;
+		private String path;
+		private OutputStream out;
+	}
+
+	private static class OpenImpl implements FileService.Open
+	{
+		public OpenImpl(File f) throws FileNotFoundException{
+			if (f != null){
+				path = f.getPath();
+				name = getFileNameFromPath(path,false);
+				in = new BufferedInputStream(new FileInputStream(f));
+			}
+		}
+
+		@Override
+		public String getName() { return name; }
+		@Override
+		public String getPath() { return path; }
+		@Override
+		public InputStream getInputStream() { return in; }
+
+		private String path;
+		private String name;
+		private InputStream in;
+	}
+
+	/**
    Edits the file path so that it ends in the desired 
    extension.
    @param original the file to use as a starting point
@@ -221,34 +261,34 @@
    or a | separated list of extensions
    @return original if it already has the desired 
    extension, or a new file with the edited file path
-   */
-   public static String editExtension(String original,
-   		String toBeRemoved, String desired){
-   	if (original == null) return null;
-   	int n = desired.indexOf('|');
-   	if (n >= 0) desired = desired.substring(0, n);
-      String path = original;
-   	if (!path.toLowerCase().endsWith(desired.toLowerCase())){   		
-   		if (toBeRemoved != null && path.toLowerCase().endsWith(
-   				toBeRemoved.toLowerCase()))
-   			path = path.substring(0, path.length() - toBeRemoved.length());
-         path = path + desired;
-      }
-   	return path;      
-   }
-   
-   public static String getFileNameFromPath(String path,boolean keepExtension){
-	   int index = path.lastIndexOf(System.getProperty("file.separator"));
-       String name;
-	   if(index == -1)
-       	  name = path;
-       else
-       	  name = path.substring(index+1);
-	   if(!keepExtension){
-		   index = name.lastIndexOf('.');
-		   if(index != -1)
-			   name = name.substring(0, index);
-	   }
-	   return name;
-   }
+	 */
+	public static String editExtension(String original,
+			String toBeRemoved, String desired){
+		if (original == null) return null;
+		int n = desired.indexOf('|');
+		if (n >= 0) desired = desired.substring(0, n);
+		String path = original;
+		if (!path.toLowerCase().endsWith(desired.toLowerCase())){   		
+			if (toBeRemoved != null && path.toLowerCase().endsWith(
+					toBeRemoved.toLowerCase()))
+				path = path.substring(0, path.length() - toBeRemoved.length());
+			path = path + desired;
+		}
+		return path;      
+	}
+
+	public static String getFileNameFromPath(String path,boolean keepExtension){
+		int index = path.lastIndexOf(System.getProperty("file.separator"));
+		String name;
+		if(index == -1)
+			name = path;
+		else
+			name = path.substring(index+1);
+		if(!keepExtension){
+			index = name.lastIndexOf('.');
+			if(index != -1)
+				name = name.substring(0, index);
+		}
+		return name;
+	}
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Finder.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Finder.java	Wed Apr 25 17:09:09 2012 +0100
@@ -22,7 +22,7 @@
 import java.util.Collection;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 
 /**
  * 
@@ -101,8 +101,8 @@
 	 * @param root
 	 * @return
 	 */
-	public static DiagramModelTreeNode findTreeNode(int[] path, DiagramModelTreeNode root){
-		DiagramModelTreeNode retVal = root;
+	public static DiagramTreeNode findTreeNode(int[] path, DiagramTreeNode root){
+		DiagramTreeNode retVal = root;
 		for(int i=0;i<path.length;i++){
 			if(retVal.getChildCount() <= path[i])
 				return null;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphElement.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphElement.java	Wed Apr 25 17:09:09 2012 +0100
@@ -24,24 +24,69 @@
 
 
 /**
- * An interface implemented by {@link Node} and {@link Edge} and it defines methods that 
- * both the classes implements as they're object painted on a graph. The interface is used mainly
- * for convenience in treating the two types of object in a unified way.  
+ * An interface implemented by {@code Node}, {@code Edge} and {@code Edge.InnerPoint}. It defines methods that 
+ * all objects painted on a graph share. The interface is used mainly
+ * for convenience in treating the different types of object in a uniformly.  
  *
  */
 public interface GraphElement {
+	/**
+	 * Draw the graphic element on a canvas
+	 * 
+	 * @param g2 the graphics object. Use {@code g2.draw()} to get things painted on the graph. 
+	 */
 	public void draw(Graphics2D g2);
 
-	public void translate(Point2D p, double dx, double dy);
+	/**
+	 * Translates this graphic element on the graph 
+	 * 
+	 * @param p the starting point where the translation starts (normally where the user clicks
+	 * with their mouse)
+	 * @param dx the distance to translate along the x-axis
+	 * @param dy the distance to translate along the y-axis
+	 * @param source the source of the translate action
+	 */
+	public void translate(Point2D p, double dx, double dy, Object source);
 
-	public void stopMove();
+	/**
+	 * This method is to be called before translation or any other operation that changes the 
+	 * position of the graph element or any of its parts. 
+	 * 
+	 * @param p the starting point of the motion 
+	 * @param source the source of the motion action
+	 */
+	public void startMove(Point2D p, Object source);
+
+	/**
+	 * This method is to be called when the motion (e.g. a translation) is over. 
+	 * Note that for instance a translation might be composed on several calls to {@code translate}
+	 * 
+	 * @param source
+	 */
+	public void stopMove(Object source);
 	
-	public void startMove(Point2D p);
-	
+	/**
+	 * Gets the bounding {@code Rectangle} of this graph element.
+	 * 
+	 * @return a new {@code Rectangle} equals to the bounding {@code Rectangle} of this graph element
+	 */
 	public Rectangle2D getBounds();
 	
+	/**
+	 * Returns the point where an line, with a specified direction, would come in contact with the outline of this 
+	 * graph element. 
+	 * 
+	 * @param d the direction of the line 
+	 * @return a new point on the outline if this graph element where the line comes in contact with it
+ 	 */
 	public Point2D getConnectionPoint(Direction d);
 	
+	/**
+	 * Tests if a specified {@code Point2D} is inside the boundary of this graph element.
+	 * 
+	 * @param p the point to be tested
+	 * @return {@code true} if the point is inside the boundary, {@code false} otherwise
+	 */
 	public boolean contains(Point2D p);
 	
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java	Wed Apr 25 17:09:09 2012 +0100
@@ -24,9 +24,7 @@
 import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.event.ActionEvent;
 import java.awt.event.InputEvent;
-import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionAdapter;
@@ -40,19 +38,19 @@
 import java.util.ResourceBundle;
 import java.util.Set;
 
-import javax.swing.AbstractAction;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
-import javax.swing.KeyStroke;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.network.Command;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 
 /**
@@ -89,73 +87,6 @@
 
 		toolbar.addEdgeCreatedListener(new innerEdgeListener());
 
-		getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0),"delete");
-		getActionMap().put("delete", new AbstractAction(){
-			@Override
-			public void actionPerformed(ActionEvent evt) {
-				/* nothing selected DELETE key has no effect */
-				if(selectedElements.isEmpty())
-					return;
-				/* create a new Set to maintain iterator consistency as elementTakenOut will change selectedItems  */
-				HashSet<DiagramElement> iterationSet = new HashSet<DiagramElement>(selectedElements); 
-				HashSet<DiagramElement>alreadyLockedElements = new HashSet<DiagramElement>();
-				/* check which, of the selected elements, can be deleted and which ones are currently held by     *
-				 * other clients. If an element is locked it's removed from the list and put into a separated set */
-				for(Iterator<DiagramElement> itr=iterationSet.iterator(); itr.hasNext();){
-					DiagramElement  selected = itr.next();
-					if(!modelUpdater.getLock(selected, Lock.DELETE)){
-						itr.remove();
-						alreadyLockedElements.add(selected);
-					}
-				}
-				ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
-				/* all the elements are locked by other clients */
-				if(iterationSet.isEmpty()){
-					iLog("Could not get lock on any selected element for deletion","");
-					JOptionPane.showMessageDialog(
-							JOptionPane.getFrameForComponent(GraphPanel.this), 
-							alreadyLockedElements.size() == 1 ? // singular vs plural 
-									resources.getString("dialog.lock_failure.delete") :
-										resources.getString("dialog.lock_failure.deletes"));
-					return;
-				}
-
-				String warning = "";
-				if(!alreadyLockedElements.isEmpty()){
-					StringBuilder builder = new StringBuilder(resources.getString("dialog.lock_failure.deletes_warning"));
-					for(DiagramElement alreadyLocked : alreadyLockedElements)
-						builder.append(alreadyLocked.getName()).append(' ');
-					warning = builder.append('\n').toString();
-					iLog("Could not get lock on some selected element for deletion",warning);
-				}
-
-				iLog("open delete dialog",warning);
-				int answer = JOptionPane.showConfirmDialog(
-						JOptionPane.getFrameForComponent(GraphPanel.this),
-						warning+resources.getString("dialog.confirm.deletions"), 
-						resources.getString("dialog.confirm.title"),
-						SpeechOptionPane.YES_NO_OPTION);
-				if(answer == JOptionPane.YES_OPTION){
-					/* the user chose to delete the elements, proceed (locks       *
-					 * will be automatically removed upon deletion by the server ) */
-					for(DiagramElement selected : iterationSet)
-						modelUpdater.takeOutFromCollection(selected);
-				}else{
-					/* the user chose not to delete the elements, release the acquired locks */
-					for(DiagramElement selected : iterationSet){
-						/* if it's a node all its attached edges were locked as well */
-						/*if(selected instanceof Node){ DONE IN THE SERVER
-						Node n = (Node)selected;
-						for(int i=0; i<n.getEdgesNum();i++){
-							modelUpdater.yieldLock(n.getEdgeAt(i), Lock.DELETE);
-						}
-					}*/
-						modelUpdater.yieldLock(selected, Lock.DELETE);
-					}
-					iLog("cancel delete node dialog","");
-				}
-			}});
-
 		/* 	---- COLLECTION LISTENER ----
 		 * Adding a collection listener. This listener reacts at changes in the model 
 		 * by any source, and thus the graph itself. Basically it refreshes the graph 
@@ -170,7 +101,7 @@
 				else
 					edges.add((Edge)element);
 				checkBounds(element,false);
-				if(e.getDiagramElement() instanceof Node && e.getSource().equals(model) ){ //FIXME change model into this model source changes 
+				if(e.getDiagramElement() instanceof Node && e.getSource().equals(DiagramEventSource.GRPH)){
 					setElementSelected(e.getDiagramElement());
 					dragMode = DRAG_NODE;
 				}
@@ -181,12 +112,12 @@
 			public void elementTakenOut(final CollectionEvent e) {
 				DiagramElement element = e.getDiagramElement(); 
 				if(element instanceof Node){
-					if(nodePopup != null && nodePopup.nodeRef.equals(element))
+					if(nodePopup != null && nodePopup.getElement().equals(element))
 						nodePopup.setVisible(false);
 					nodes.remove(element);
 				}
 				else{
-					if(edgePopup != null && edgePopup.edgeRef.equals(element))
+					if(edgePopup != null && edgePopup.getElement().equals(element))
 						edgePopup.setVisible(false);
 					edges.remove(element);
 				}
@@ -229,17 +160,17 @@
 						if( e.contains(mousePoint)){
 							Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST);
 							if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item
-								EdgePopupMenu pop = new EdgePopupMenu(e,GraphPanel.this,modelUpdater);
+								CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,GraphPanel.this,modelUpdater,selectedElements);
 								edgePopup = pop;
 								pop.show(GraphPanel.this, event.getX(), event.getY());
 							}else{ // click near an attached nodes, prompt for name change, set end label and select arrow head
-								EdgePopupMenu pop = new EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater);
+								CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater,selectedElements);
 								edgePopup = pop;
 								pop.show(GraphPanel.this, event.getX(), event.getY());
 							}
 						}
 					}else if(n != null){
-						NodePopupMenu pop = new NodePopupMenu(n,GraphPanel.this,modelUpdater);
+						CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements);
 						nodePopup = pop;
 						pop.show(GraphPanel.this, event.getX(), event.getY());
 					}else
@@ -249,10 +180,11 @@
 				/* - one click && palette == select - */
 				else if (tool == null){
 					if(n != null){ // node selected
-						if (isCtrl)
+						if (isCtrl){
 							addElementToSelection(n,false);
-						else
+						}else{
 							setElementSelected(n);
+						}
 						dragMode = DRAG_NODE;
 					}else if (e != null){ // edge selected
 						if (isCtrl){
@@ -260,7 +192,7 @@
 							dragMode = DRAG_NODE;
 						}else{
 							setElementSelected(e);
-							modelUpdater.startMove(e, mousePoint);
+							modelUpdater.startMove(e, mousePoint,DiagramEventSource.GRPH);
 							dragMode = DRAG_EDGE;
 						}
 					}else{  // nothing selected : make selection lasso
@@ -284,11 +216,11 @@
 						Rectangle2D bounds = newNode.getBounds();
 						/* perform the translation from the origin */
 						newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(), 
-								mousePoint.getY() - bounds.getY());
+								mousePoint.getY() - bounds.getY(),DiagramEventSource.NONE);
 						/* log stuff */
 						iLog("insert node",""+((newNode.getId() == DiagramElement.NO_ID) ? "(no id)" : newNode.getId()));
 						/* insert the node into the model (no lock needed) */
-						modelUpdater.insertInCollection(newNode);
+						modelUpdater.insertInCollection(newNode,DiagramEventSource.GRPH);
 					}	
 				}
 
@@ -304,21 +236,31 @@
 						(event.getY()+minY)/zoom 
 				);
 				if(lastSelected != null){
-					if(lastSelected instanceof Node){
+					if(lastSelected instanceof Node || selectedElements.size() > 1){ // differentiate between translate and edge bending
 						if(wasMoving){
 							iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY());
 							for(Object element : moveLockedElements){
-								modelUpdater.stopMove((GraphElement)element);
-								modelUpdater.yieldLock((DiagramModelTreeNode)element, Lock.MOVE);
+								modelUpdater.stopMove((GraphElement)element,DiagramEventSource.GRPH);
+								boolean isNode = element instanceof Node;
+								modelUpdater.yieldLock((DiagramTreeNode)element, 
+										Lock.MOVE,
+										new DiagramEventActionSource(
+												DiagramEventSource.GRPH, 
+												isNode ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
+												((DiagramElement)element).getId(),((DiagramElement)element).getName()));
 							}
 							moveLockedElements.clear();
 						}
-					}else{ // instanceof Edge 
+					}else{ // instanceof Edge  && selectedelements.size() = 1. Bending
 						if(wasMoving){
 							iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY());
 							if(moveLockedEdge != null){
-								modelUpdater.stopMove(moveLockedEdge);
-								modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE);
+								modelUpdater.stopMove(moveLockedEdge,DiagramEventSource.GRPH);
+								modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE, new DiagramEventActionSource(
+										DiagramEventSource.GRPH,
+										Command.Name.BEND,
+										moveLockedEdge.getId(),
+										moveLockedEdge.getName()));
 								moveLockedEdge = null;
 							}
 						}
@@ -348,7 +290,14 @@
 						Iterator<DiagramElement> iterator = selectedElements.iterator();
 						while(iterator.hasNext()){
 							DiagramElement element = iterator.next(); 
-							if(modelUpdater.getLock(element, Lock.MOVE)){
+							boolean isNode = element instanceof Node;
+							if(modelUpdater.getLock(element, 
+									Lock.MOVE,
+									new DiagramEventActionSource(
+											DiagramEventSource.GRPH, 
+											isNode ? Command.Name.TRANSLATE_NODE : Command.Name.TRANSLATE_EDGE,
+											element.getId(),
+											element.getName()))){
 								moveLockedElements.add(element);
 							}else{
 								iLog("Could not get move lock for element",DiagramElement.toLogString(element));
@@ -360,21 +309,29 @@
 
 					for (DiagramElement selected : selectedElements){
 						if(selected instanceof Node)
-							modelUpdater.translate((Node)selected, lastMousePoint, dx, dy);
+							modelUpdater.translate((Node)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
 						else
-							modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy);
+							modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
 					}
 				} else if(dragMode == DRAG_EDGE){
 					if(!wasMoving){
 						wasMoving = true;
-						if(modelUpdater.getLock(lastSelected, Lock.MOVE))
+						if(modelUpdater.getLock(lastSelected, 
+								Lock.MOVE, 
+								new DiagramEventActionSource(
+										DiagramEventSource.GRPH, 
+										Command.Name.BEND,
+										lastSelected.getId(),
+										lastSelected.getName()))
+						){
 							moveLockedEdge = (Edge)lastSelected;
-						else
+						}else{
 							iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected));
+						}
 						iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY());
 					}
 					if(moveLockedEdge != null)
-						modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY()));
+						modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY()),DiagramEventSource.GRPH);
 				} else if (dragMode == DRAG_LASSO){
 					double x1 = mouseDownPoint.getX();
 					double y1 = mouseDownPoint.getY();
@@ -698,7 +655,8 @@
 			}
 			try {
 				e.connect(nodesToConnect);
-				modelUpdater.insertInCollection(e);
+				/* perform the command, no lock is needed for inserting */
+				modelUpdater.insertInCollection(e,DiagramEventSource.GRPH);
 			} catch (ConnectNodesException cnEx) {
 				JOptionPane.showMessageDialog(GraphPanel.this,
 						cnEx.getLocalizedMessage(),
@@ -716,8 +674,8 @@
 
 	private Grid grid;
 	private GraphToolbar toolbar;
-	private NodePopupMenu nodePopup;
-	private EdgePopupMenu edgePopup;
+	private CCmIPopupMenu.NodePopupMenu nodePopup;
+	private CCmIPopupMenu.EdgePopupMenu edgePopup;
 
 	private double zoom;
 	private double gridSize;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticKindle.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticKindle.java	Wed Apr 25 17:09:09 2012 +0100
@@ -20,6 +20,7 @@
 
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.util.ResourceBundle;
 
 import javax.swing.SwingUtilities;
 
@@ -27,8 +28,11 @@
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
 import uk.ac.qmul.eecs.ccmi.haptics.HapticListener;
 import uk.ac.qmul.eecs.ccmi.haptics.HapticListenerCommand;
+import uk.ac.qmul.eecs.ccmi.main.DiagramEditorApp;
+import uk.ac.qmul.eecs.ccmi.network.Command;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
-import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 
@@ -45,94 +49,94 @@
 		unselectRunnable = new Runnable(){
 			@Override
 			public void run(){
+				EditorFrame frame = DiagramEditorApp.getFrame();
+				if((frame == null)||(frame.getActiveTab() == null))
+					return;
 				frame.selectHapticHighligh(null);
 			}
 		};
-		frameBackupRunnable = new Runnable(){
-			@Override
-			public void run(){
-				frame.backupOpenDiagrams();
-			}
-		};
 	}
 
-	public void setEditorFrame(EditorFrame frame){
-		this.frame = frame;
-	}
-	
+	/**
+	 * Implementation of the {@code executeCommand} method. All the commands that involve the 
+	 * diagram model, are executed in the Event Dispatching Thread through {@code SwingUtilities} invoke
+	 * methods. This prevents race conditions on the model and on diagram elements. 
+	 * 
+	 * @see HapticListener#executeCommand(HapticListenerCommand, int, double, double, double, double)
+	 */
 	@Override
-	public void executeCommand(HapticListenerCommand cmd, int ID, final double x, final double y, final double startX, final double startY) {
-		if((frame == null)||(frame.getActiveTab() == null))
-			return;
-		CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
-		Object monitor = collectionModel.getMonitor();
-		DiagramElement de = null;
+	public void executeCommand(HapticListenerCommand cmd, final int ID, final double x, final double y, final double startX, final double startY) {
+		final EditorFrame frame = DiagramEditorApp.getFrame();
 		switch(cmd){
-		case PLAY_ELEMENT_SOUND :
-			synchronized(monitor){
-				de = Finder.findElementByHashcode(ID, collectionModel.getElements());
-			}
-			/* can be null if the tab has been switched or closed in the meantime */
-			if(de == null)
-				return;
-			SoundFactory.getInstance().play(de.getSound());
+		case PLAY_ELEMENT_SOUND :  
+			SwingUtilities.invokeLater(new Runnable(){
+				public void run(){
+					if((frame == null)||(frame.getActiveTab() == null))
+						return;
+					CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
+					DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
+					/* can be null if the tab has been switched or closed in the meantime */
+					if(de == null)
+						return;
+					SoundFactory.getInstance().play(de.getSound());
+				}
+			});
 			break;
 		case PLAY_ELEMENT_SPEECH : 
-			synchronized(monitor){
-				de = Finder.findElementByHashcode(ID, collectionModel.getElements());
-			}
-			if(de == null)
-				return;
-			SoundFactory.getInstance().play(de.getSound());
-			NarratorFactory.getInstance().speak(de.getName());
-			iLog("touch",((de instanceof Node) ? "node " : "edge ")+de.getName());
+			SwingUtilities.invokeLater(new Runnable(){
+				public void run(){
+					if((frame == null)||(frame.getActiveTab() == null))
+						return;
+					CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
+					DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
+					if(de == null)
+						return;
+					SoundFactory.getInstance().play(de.getSound());
+					NarratorFactory.getInstance().speak(de.getName());
+					iLog("touch",((de instanceof Node) ? "node " : "edge ")+de.getName());
+				}
+			});
 			break;
 		case SELECT :
-			synchronized(monitor){
-				de = Finder.findElementByHashcode(ID, collectionModel.getElements());
-			}
-			if(de == null)
-				return;
-			final DiagramElement selectedElement = de; 
 			SwingUtilities.invokeLater(new Runnable(){
-				@Override
 				public void run(){
-					frame.selectHapticHighligh(selectedElement);
+					if((frame == null)||(frame.getActiveTab() == null))
+						return;
+					CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
+					DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
+					if(de == null)
+						return;
+					frame.selectHapticHighligh(de);
 				}
 			});
 			break;
 		case UNSELECT :
 			SwingUtilities.invokeLater(unselectRunnable);
 			break;
-		case MOVE :
-			DiagramPanel dPanel = frame.getActiveTab();
-			if(dPanel == null)
-				return;
-			synchronized(monitor){
-				de = Finder.findElementByHashcode(ID, collectionModel.getElements());
-			}
-			if(de == null)
-				return;
-			final DiagramElement moveSelectedElement = de;
-			final DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
-			if(!modelUpdater.getLock(moveSelectedElement, Lock.MOVE)){
-				iLog("Could not get lock on element for motion", DiagramElement.toLogString(moveSelectedElement));
-				NarratorFactory.getInstance().speak("Object is being moved by another user");
-				return;
-			}
-			
+		case MOVE : {
+			/* when this block is executed we already have the lock * 
+			 * on the element from the PICK_UP command execution    */
 			try {
 				SwingUtilities.invokeAndWait(new Runnable(){
 					@Override
 					public void run(){
-						if(moveSelectedElement instanceof Node){
-							Node n = (Node)moveSelectedElement;
+						if((frame == null)||(frame.getActiveTab() == null))
+							return;
+						CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
+						DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
+						if(de == null)
+							return;
+						DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater();
+						if(de instanceof Node){
+							Node n = (Node)de;
 							Rectangle2D bounds = n.getBounds();
 							Point2D.Double p = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
 							double dx = x - p.getX();
 							double dy = y - p.getY();
-							modelUpdater.translate(n, p, dx, dy);
-							modelUpdater.stopMove(n);
+							n.getMonitor().lock();
+							modelUpdater.translate(n, p, dx, dy,DiagramEventSource.HAPT);
+							modelUpdater.stopMove(n,DiagramEventSource.HAPT);
+							n.getMonitor().unlock();
 
 							StringBuilder builder = new StringBuilder();
 							builder.append(DiagramElement.toLogString(n)).append(" ").append(p.getX())
@@ -141,16 +145,16 @@
 							builder = new StringBuilder();
 							builder.append(DiagramElement.toLogString(n)).append(' ')
 							.append(x).append(' ').append(y);
-
 							iLog("move node end",builder.toString());
+						}else{
+							Edge e = (Edge)de;
+							modelUpdater.startMove(e, new Point2D.Double(startX,startY),DiagramEventSource.HAPT);
+							Point2D p  = new Point2D.Double(x,y);
+							e.getMonitor().lock();
+							modelUpdater.bend(e, p,DiagramEventSource.HAPT);
+							modelUpdater.stopMove(e,DiagramEventSource.HAPT);
+							e.getMonitor().unlock();
 							
-						}else{
-							Edge e = (Edge)moveSelectedElement;
-							modelUpdater.startMove(e, new Point2D.Double(startX,startY));
-							Point2D p  = new Point2D.Double(x,y);
-							modelUpdater.bend(e, p);
-							modelUpdater.stopMove(e);
-
 							StringBuilder builder = new StringBuilder();
 							builder.append(DiagramElement.toLogString(e)).append(' ').append(startX)
 							.append(' ').append(startY);
@@ -159,25 +163,37 @@
 							builder.append(DiagramElement.toLogString(e)).append(' ')
 							.append(x).append(' ').append(y);
 							iLog("bend edge end",builder.toString());
-							
 						}
-					}
+						modelUpdater.yieldLock(de,
+								Lock.MOVE,
+								new DiagramEventActionSource(
+										DiagramEventSource.HAPT,
+										de instanceof Node ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
+										de.getId(),
+										de.getName()
+										));
+					} // run()
 				});
 			} catch (Exception e) {
 				throw new RuntimeException(e);
 			}
 			SoundFactory.getInstance().play(SoundEvent.HOOK_OFF);
-			modelUpdater.yieldLock(moveSelectedElement, Lock.MOVE);
+		}
 			break;
-		case INFO :
-			synchronized(monitor){
-				de = Finder.findElementByHashcode(ID, collectionModel.getElements());
-			}
-			if(de == null)
-				return;
-			SoundFactory.getInstance().stop();
-			NarratorFactory.getInstance().speak(de.detailedSpokenText());
-			iLog("request detailed info",((de instanceof Node) ? "node " : "edge ")+de.getName());
+		case INFO : 
+			SwingUtilities.invokeLater(new Runnable(){
+				public void run(){
+					if((frame == null)||(frame.getActiveTab() == null))
+						return;
+					CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
+					DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
+					if(de == null)
+						return;
+					SoundFactory.getInstance().stop();
+					NarratorFactory.getInstance().speak(de.detailedSpokenText());
+					iLog("request detailed info",((de instanceof Node) ? "node " : "edge ")+de.getName());					
+				}
+			});
 			break;
 		case PLAY_SOUND :
 			switch(HapticListenerCommand.Sound.fromInt(ID) ){
@@ -189,19 +205,50 @@
 				SoundFactory.getInstance().play(SoundEvent.MAGNET_ON);
 				iLog("sticky mode on","");
 				break;
-			case HOOK_ON : 
-				SoundFactory.getInstance().play(SoundEvent.HOOK_ON);
-				iLog("hook on","");
-				break;
 			case DRAG : SoundFactory.getInstance().play(SoundEvent.DRAG);
 				break;
 			}
 			break;
+		case PICK_UP :
+			try {
+				SwingUtilities.invokeAndWait(new Runnable (){
+					@Override
+					public void run(){
+						if((frame == null)||(frame.getActiveTab() == null))
+							return;
+						CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
+						DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
+						if(de == null)
+							return;
+						DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater();
+						if(!modelUpdater.getLock(de, 
+								Lock.MOVE,
+								new DiagramEventActionSource(DiagramEventSource.HAPT, de instanceof Edge ? Command.Name.TRANSLATE_EDGE : Command.Name.TRANSLATE_NODE ,de.getId(),de.getName()))){
+							iLog("Could not get lock on element for motion", DiagramElement.toLogString(de));
+							NarratorFactory.getInstance().speak("Object is being moved by another user");
+							return;
+						}
+						frame.hPickUp(de);
+						SoundFactory.getInstance().play(SoundEvent.HOOK_ON);
+						iLog("hook on","");
+					}
+				});
+			}catch(Exception e){
+				e.printStackTrace();
+				throw new RuntimeException();	
+			}
+			break;
 		case ERROR : 
 			/* no synchronization necessary as the XMLManager looks after it*/
-			SwingUtilities.invokeLater(frameBackupRunnable);
-			NarratorFactory.getInstance().speak("Haptic device crashed. " +
-					"Unsaved diagrams saved in backup directory");
+			SwingUtilities.invokeLater(new Runnable(){
+				@Override
+				public void run(){
+					if((frame == null)||(frame.getActiveTab() == null))
+						return;
+					frame.backupOpenDiagrams();
+				}
+			});
+			NarratorFactory.getInstance().speak(ResourceBundle.getBundle(EditorFrame.class.getName()).getString("speech.haptic_device_crashed"));
 			break;
 		}
 	}
@@ -211,7 +258,6 @@
 	}
 	
 	private Runnable unselectRunnable;
-	private Runnable frameBackupRunnable;
 	private static String INTERACTION_LOG_SOURCE = "HAPTIC"; 
-	private EditorFrame frame;
+	//private EditorFrame frame;
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticTrigger.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticTrigger.java	Wed Apr 25 17:09:09 2012 +0100
@@ -33,40 +33,51 @@
 
 	@Override
 	public void elementInserted(CollectionEvent evt) {
+		DiagramEventSource source = (DiagramEventSource)evt.getSource(); 
 		if(evt.getDiagramElement() instanceof Node){
 			Node n = (Node)evt.getDiagramElement();
-			HapticsFactory.getInstance().addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n));
+			HapticsFactory.getInstance().addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),source.getDiagramName());
 		}else{//edge
 			Edge e = (Edge)evt.getDiagramElement();
 			Edge.PointRepresentation pr = e.getPointRepresentation();
-			HapticsFactory.getInstance().addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine());
+			HapticsFactory.getInstance().addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),source.getDiagramName());
 		}
 	}
 
 	@Override
 	public void elementTakenOut(CollectionEvent evt) {
+		DiagramEventSource source = (DiagramEventSource)evt.getSource(); 
 		if(evt.getDiagramElement() instanceof Node){
 			Node n = (Node)evt.getDiagramElement();
-			HapticsFactory.getInstance().removeNode(System.identityHashCode(n));
+			HapticsFactory.getInstance().removeNode(System.identityHashCode(n),source.getDiagramName());
 		}else{//edge
 			Edge e = (Edge)evt.getDiagramElement();
-			HapticsFactory.getInstance().removeEdge(System.identityHashCode(e));
+			HapticsFactory.getInstance().removeEdge(System.identityHashCode(e),source.getDiagramName());
 		}
 	}
 
 	@Override
 	public void elementChanged(ElementChangedEvent evt) {
+		DiagramEventSource source = (DiagramEventSource)evt.getSource(); 
 		if("stop_move".equals(evt.getChangeType())){
 			if(evt.getDiagramElement() instanceof Edge){
 				Edge e = (Edge)evt.getDiagramElement();
 				Edge.PointRepresentation pr = e.getPointRepresentation();
-				HapticsFactory.getInstance().updateEdge(System.identityHashCode(e), pr.xs, pr.ys,pr.adjMatrix,pr.nodeStart, e.getNameLine());
+				HapticsFactory.getInstance().updateEdge(
+						System.identityHashCode(e), 
+						pr.xs, 
+						pr.ys,
+						pr.adjMatrix,
+						pr.nodeStart, 
+						e.getNameLine(),
+						source.getDiagramName());
 			}else{
 				Node n = (Node)evt.getDiagramElement();		
 				HapticsFactory.getInstance().moveNode(
 						n.getBounds().getCenterX(),
 						n.getBounds().getCenterY(),
-						System.identityHashCode(n)
+						System.identityHashCode(n),
+						source.getDiagramName()
 				);
 			}
 		}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/LineStyle.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/LineStyle.java	Wed Apr 25 17:09:09 2012 +0100
@@ -27,27 +27,48 @@
  *
  */
 public enum LineStyle {
-	Solid(new BasicStroke()),
+	Solid(new BasicStroke(),0xFFFF),
 	Dotted(new BasicStroke(1.0f, 
 			BasicStroke.CAP_ROUND, 
 			BasicStroke.JOIN_ROUND, 
 			0.0f, 
 			new float[]{1.0f,3.0f}, 
-			0.0f)),
+			0.0f),0xF0F0),
 	Dashed(new BasicStroke(1.0f, 
 			BasicStroke.CAP_ROUND, 
 			BasicStroke.JOIN_ROUND, 
 			0.0f, 
 			new float[]{5.0f,5.0f}, 
-			0.0f));
+			0.0f),0xAAAA);
 	
-	private LineStyle(BasicStroke stroke){
+	private LineStyle(BasicStroke stroke, int stipplePattern){
 		this.stroke = stroke;
+		this.stipplePattern = stipplePattern;
 	}
 
+	/**
+	 * returns the stroke of this line style. The stroke is used to paint 
+	 * the edge that has this line style on a graphics.  
+	 *  
+	 * @return the stroke for this line style
+	 */
 	public Stroke getStroke(){
 		return stroke;
 	}
 	
+	/**
+	 * Returns an a bit representation of the stippling of this edge. 
+	 * This value can be used by openGL like libraries to draw the edge and it's used by 
+	 * the OmniHaptic device native code to paint the edge visually and haptically.  
+	 * See also {@link http://www.opengl.org/sdk/docs/man/xhtml/glLineStipple.xml}
+	 *
+	 * 
+	 * @return an int with the bit representation of the stipple pattern
+	 */
+	public int getStipplePattern(){
+		return stipplePattern;
+	}
+	
 	private Stroke stroke;
+	private int stipplePattern; 
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java	Wed Apr 25 17:09:09 2012 +0100
@@ -35,6 +35,7 @@
 import org.w3c.dom.NodeList;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
@@ -112,12 +113,12 @@
 	}
 
 	@Override
-	public void stopMove(){
-		notifyChange(new ElementChangedEvent(this,this,"stop_move"));
+	public void stopMove(Object source){
+		notifyChange(new ElementChangedEvent(this,this,"stop_move",source));
 		/* edges can change as a result of nodes motion thus we call the method for all the edges
 		 * of the node regardless what the mouse point is */
 		for(int i = 0; i < getEdgesNum();i++){
-			getEdgeAt(i).stopMove();
+			getEdgeAt(i).stopMove(source);
 		}
 	}
 
@@ -127,17 +128,25 @@
 	 *	@param dx the amount to translate in the x-direction
 	 *	@param dy the amount to translate in the y-direction
 	 */
-	public void translate( Point2D p , double dx, double dy){
+	public void translate( Point2D p , double dx, double dy, Object source){
 		translateImplementation( p, dx, dy);
-		notifyChange(new ElementChangedEvent(this, this, "translate"));
+		for(int i=0; i< getInternalNodesNum();i++){
+			getInternalNodeAt(i).translate(p, dx, dy,source);
+		}
+		notifyChange(new ElementChangedEvent(this, this, "translate", source));
 	}
 
-	protected void translateImplementation(Point2D p , double dx, double dy){
-		for(int i=0; i< getInternalNodesNum();i++){
-			getInternalNodeAt(i).translate(p, dx, dy);
-		}
+	/**
+	 * @see DiagramTreeNode#setNotes(String)
+	 */
+	@Override
+	protected void setNotes(String notes,Object source){
+		this.notes = notes;
+		notifyChange(new ElementChangedEvent(this,this,"notes",source));
 	}
-
+	
+	protected abstract void translateImplementation(Point2D p , double dx, double dy);
+		
 	/**
 	 *	Tests whether the node contains a point.
 	 *	@param aPoint the point to test
@@ -153,7 +162,7 @@
 	public abstract Rectangle2D getBounds();
 
 	@Override
-	public void startMove(Point2D p){
+	public void startMove(Point2D p,Object source){
 		/* useless, here just to comply with the GraphElement interface */
 	}
 
@@ -223,7 +232,7 @@
 	}
 
 	public void decode(Document doc, Element nodeTag) throws IOException{
-		setName(nodeTag.getAttribute(PersistenceManager.NAME));
+		setName(nodeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS);
 		try{
 			setId(Integer.parseInt(nodeTag.getAttribute(PersistenceManager.ID)));
 		}catch(NumberFormatException nfe){
@@ -241,7 +250,7 @@
 			throw new IOException();
 		}
 		Rectangle2D bounds = getBounds();
-		translate(new Point2D.Double(0,0), dx - bounds.getX(), dy - bounds.getY());
+		translate(new Point2D.Double(0,0), dx - bounds.getX(), dy - bounds.getY(),DiagramEventSource.PERS);
 
 		NodeList propList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
 		NodeProperties properties = getProperties();
@@ -307,12 +316,12 @@
 								throw new IOException(nfe);
 							}
 						}
-						addProperty(propertyType, value);//whether propertyType actually exist in the prototypes has been already checked  
-						setModifierIndexes(propertyType, h, indexesToAdd);
+						addProperty(propertyType, value,DiagramEventSource.PERS);//whether propertyType actually exist in the prototypes has been already checked  
+						setModifierIndexes(propertyType, h, indexesToAdd,DiagramEventSource.PERS);
 					}else
-						addProperty(propertyType, value);
+						addProperty(propertyType, value,DiagramEventSource.PERS);
 				}else
-					addProperty(propertyType, value);
+					addProperty(propertyType, value,DiagramEventSource.PERS);
 			}
 		}
 	}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/NodePopupMenu.java	Mon Feb 06 12:54:06 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-/*  
- CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-  
- Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-package uk.ac.qmul.eecs.ccmi.gui;
-
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.text.MessageFormat;
-import java.util.ResourceBundle;
-
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPopupMenu;
-
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
-import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
-
-/**
- * A pop up menu displaying the possible operations to perform on an node on from a visual representation
- * of a diagram.
- *
- */
-@SuppressWarnings("serial")
-public class NodePopupMenu extends JPopupMenu {
-	public NodePopupMenu(Node node, Component parentComponent, DiagramModelUpdater modelUpdater){
-		nodeRef = node;
-		this.modelUpdater = modelUpdater;
-		this.parentComponent = parentComponent;
-		addMenuItems();
-	}
-	
-	
-	private void addMenuItems(){
-		/* add set name menu item*/
-		JMenuItem setNameMenuItem = new JMenuItem(resources.getString("menu.set_name"));
-		setNameMenuItem.addActionListener(new ActionListener(){
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				if(!modelUpdater.getLock(nodeRef, Lock.NAME)){
-					iLog("Could not get the lock on node for renaming",DiagramElement.toLogString(nodeRef));
-					JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.name"));
-					return;
-				}
-				iLog("open rename node dialog",DiagramElement.toLogString(nodeRef));
-				String name = JOptionPane.showInputDialog(parentComponent, MessageFormat.format(resources.getString("dialog.input.name"),nodeRef.getName()), nodeRef.getName());
-				if(name == null)
-					iLog("cancel rename node dialog",DiagramElement.toLogString(nodeRef)); 
-				else
-					/* node has been locked at selection time */
-					modelUpdater.setName(nodeRef, name.trim());
-				modelUpdater.yieldLock(nodeRef, Lock.NAME);
-			}
-			
-		});
-		add(setNameMenuItem);
-		
-		/* if the node has no properties defined, then don't add the menu item */
-		if(nodeRef.getProperties().isNull())
-			return;
-		/* add set property menu item*/
-		JMenuItem setPropertiesMenuItem = new JMenuItem(resources.getString("menu.set_properties"));
-		setPropertiesMenuItem.addActionListener(new ActionListener(){
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				if(!modelUpdater.getLock(nodeRef, Lock.PROPERTIES)){
-					iLog("Could not get the lock on node for properties",DiagramElement.toLogString(nodeRef));
-					JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.properties"));
-					modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES);
-					return;
-				}
-				iLog("open edit properties dialog",DiagramElement.toLogString(nodeRef));
-				NodeProperties properties = PropertyEditorDialog.showDialog(JOptionPane.getFrameForComponent(parentComponent),nodeRef.getPropertiesCopy()); 
-				if(properties == null){ // user clicked on cancel 
-					iLog("cancel edit properties dialog",DiagramElement.toLogString(nodeRef));
-					modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES);
-					return;
-				}
-				if(!properties.isNull())
-					modelUpdater.setProperties(nodeRef,properties);
-				modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES);
-			}
-		});
-		add(setPropertiesMenuItem);
-	}
-	
-	private void iLog(String action, String args){
-		InteractionLog.log("GRAPH",action,args);
-	}
-	
-	public Node nodeRef; 
-	private DiagramModelUpdater modelUpdater;
-	private Component parentComponent;
-	private static ResourceBundle resources = ResourceBundle.getBundle(NodePopupMenu.class.getName());
-}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/NodePopupMenu.properties	Mon Feb 06 12:54:06 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-
-
-menu.set_name=Set Name
-menu.set_properties=Set Properties
-
-dialog.lock_failure.name=Node name is being edited by another user
-dialog.lock_failure.properties=Node properties are being edited by another user
-dialog.input.name=Renaming {0}, Enter new name
\ No newline at end of file
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyEditorDialog.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyEditorDialog.java	Wed Apr 25 17:09:09 2012 +0100
@@ -177,9 +177,7 @@
 					PropertyTableModel model = propertyPanels[i].model;
 					properties.clear(model.getColumnName(0));
 					for(int j=0; j< model.getRowCount();j++){
-						String value = model.getValueAt(j, 0).toString();
-						if(value != null)
-							value = value.trim();
+						String value = model.getValueAt(j, 0).toString().trim();
 						if(!value.equals("")){
 							properties.addValue(model.getColumnName(0),value , model.getIndexesAt(j));
 						}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechLogDialog.java	Mon Feb 06 12:54:06 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*  
- CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-  
- Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-package uk.ac.qmul.eecs.ccmi.gui;
-
-import java.awt.Frame;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-
-import javax.swing.JFrame;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-import javax.swing.ScrollPaneConstants;
-
-import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
-
-/**
- * A log handler that displays log records on a JTextArea in a dedicated Frame and speaks them out
- * through text to speech synthesis performed by the {@link Narrator} instance. 
- *
- */
-public class SpeechLogDialog extends Handler {
-	public static SpeechLogDialog getSpeechLogDialog(Frame parent){
-		/* the static reference prevent the handler from being garbage collected * 
-		 * as the logger has a static management   							     */
-		if(speechLogDialog == null)
-			speechLogDialog = new SpeechLogDialog(parent);
-		return speechLogDialog;
-	}
-	
-	private SpeechLogDialog(Frame parent){
-		setLevel(Level.CONFIG);
-		
-		area = new JTextArea(ROWS,COLS);
-		area.setEditable(false);
-		area.setLineWrap(true);
-		area.addFocusListener(new FocusAdapter(){
-			@Override
-			public void	focusGained(FocusEvent e) {
-				NarratorFactory.getInstance().speak("Server window focused");
-			}
-		});
-
-		frame = new JFrame("Server");
-		frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
-		frame.getContentPane().add(new JScrollPane(area,ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER));
-		frame.pack();
-        frame.setLocationRelativeTo(parent);
-        frame.setVisible(true);
-	}
-
-	@Override
-	public void close() throws SecurityException {
-		frame.dispose();
-		speechLogDialog = null;
-	}
-
-	@Override
-	public void flush() {}
-
-	@Override
-	public void publish(LogRecord log) {
-		String prefix = "";
-		if(log.getLevel().equals(Level.WARNING)){
-			prefix = WARNING_PREFIX;
-		}
-		if(log.getLevel().equals(Level.SEVERE))
-			prefix = ERROR_PREFIX;
-		String message = prefix+log.getMessage()+'\n';
-		NarratorFactory.getInstance().speakWholeText(message);
-		area.append(message);
-	}
-	
-	JFrame frame;
-	JTextArea area;
-	private static final int ROWS = 10;
-	private static final int COLS = 50;
-	private static final String WARNING_PREFIX = "WARNING: ";
-	private static final String ERROR_PREFIX = "ERROR: ";
-	private static SpeechLogDialog speechLogDialog;
-}
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java	Wed Apr 25 17:09:09 2012 +0100
@@ -20,8 +20,12 @@
 package uk.ac.qmul.eecs.ccmi.gui;
 
 import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
@@ -33,8 +37,8 @@
 import javax.swing.MenuElement;
 import javax.swing.MenuSelectionManager;
 
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
-import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
 
 /**
@@ -53,7 +57,7 @@
 				public void	processKeyEvent(KeyEvent e, MenuElement[] path, MenuSelectionManager manager) {
 					super.processKeyEvent(e,path,manager);
 					if(e.getKeyCode() == KeyEvent.VK_ESCAPE){
-						NarratorFactory.getInstance().speak("Leaving Menu");
+						NarratorFactory.getInstance().speak(resources.getString("menufactory.leaving"));
 					}
 				}
 			};
@@ -71,11 +75,11 @@
 			public void menuSelectionChanged(boolean isIncluded){
 				  super.menuSelectionChanged(isIncluded);
 				  if(isIncluded && !wasMouse){
-					  String menuType = " menu";
+					  String menuType = resources.getString("menufactory.menu");
 					  if(getMenuBar().getComponentIndex(this) == -1){
-						  menuType = " sub menu";
+						  menuType = resources.getString("menufactory.submenu");
 					  };
-					  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName()+menuType);
+					  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName()+ " "+ menuType);
 				  }
 				  wasMouse = false;
 			}
@@ -91,32 +95,13 @@
 	}
 	
 	public static JCheckBoxMenuItem getJCheckBoxMenuItem(String text){
-		return new JCheckBoxMenuItem(text){
-			@Override
-			public void menuSelectionChanged(boolean isIncluded){
-				  super.menuSelectionChanged(isIncluded);
-				  if(isIncluded && !wasMouse ){
-					  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName());
-				  }
-				  wasMouse = false;
-			}
-			
-			@Override
-			public void processMouseEvent(MouseEvent e){
-				wasMouse = true;
-				super.processMouseEvent(e);
-			}
-			
-			private  boolean wasMouse = false;
-		};
+		return new SpeechJCheckBoxMenuItem(text);
 	}
 	
 	/* this action is called when the user strokes a disabled menu accelerator */
 	private static Action errorAction = new AbstractAction(){
 		@Override
 		public void actionPerformed(ActionEvent e) {
-			/* clear from any previously registered playerListener to get sound only */
-			SoundFactory.getInstance().unsetPlayerListener(SoundEvent.ERROR);
 			SoundFactory.getInstance().play(SoundEvent.ERROR);
 		}
 	};
@@ -145,7 +130,7 @@
 			if(text.trim().endsWith("...")){
 				/* replace the ... in the accessible name  with the voice version of it */
 				String accName = getAccessibleContext().getAccessibleName().replaceAll("...\\s*$", "");
-				getAccessibleContext().setAccessibleName(accName + " dot dot dot");
+				getAccessibleContext().setAccessibleName(accName +" "+ resources.getString("menufactory.3dot"));
 			}
 		}
 		
@@ -153,10 +138,10 @@
 		public void menuSelectionChanged(boolean isIncluded){
 			  super.menuSelectionChanged(isIncluded);
 			  if(isIncluded && !wasMouse){
-				  String disabled = isEnabled() ? "" : ", disabled";
+				  String disabled = isEnabled() ? "" : resources.getString("menufactory.disabled");
 				  String accelerator = "";
 				  if(getAccelerator() != null){
-					  accelerator = ", control " + getAccelerator().toString().substring(getAccelerator().toString().lastIndexOf(' '));
+					  accelerator = resources.getString("menufactory.ctrl")+" "+ getAccelerator().toString().substring(getAccelerator().toString().lastIndexOf(' '));
 				  }
 				  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName()+disabled+accelerator);
 			  }
@@ -180,5 +165,50 @@
 		private boolean wasMouse;
 	};
 	
+	private static class SpeechJCheckBoxMenuItem extends JCheckBoxMenuItem {
+		public SpeechJCheckBoxMenuItem(String text){
+			super(text);
+			addItemListener(new ItemListener(){
+				  @Override
+				  public void itemStateChanged(ItemEvent evt) {
+					  int stateChange = evt.getStateChange(); 
+					  if(stateChange != ItemEvent.SELECTED && stateChange != ItemEvent.DESELECTED){
+						  return;
+					  }
+					  if(!itemChangeMouseFlag){
+						  NarratorFactory.getInstance().speak(
+								  MessageFormat.format(
+										  resources.getString(stateChange == ItemEvent.SELECTED ? "menufactory.selected" : "menufactory.unselected"), 
+										  getAccessibleContext().getAccessibleName()));
+					  }
+					  itemChangeMouseFlag = false;
+				  }
+			  });
+		}
+		
+		@Override
+		public void menuSelectionChanged(boolean isIncluded){
+			  super.menuSelectionChanged(isIncluded);
+			  if(isIncluded && !selectionMouseFlag ){
+				  NarratorFactory.getInstance().speak(
+						  MessageFormat.format(
+								  resources.getString(isSelected() ? "menufactory.selected" : "menufactory.unselected"), 
+								  getAccessibleContext().getAccessibleName()));
+			  }
+			  selectionMouseFlag = false;
+		}
+		
+		@Override
+		public void processMouseEvent(MouseEvent e){
+			selectionMouseFlag = true;
+			itemChangeMouseFlag = true;
+			super.processMouseEvent(e);
+		}
+		
+		private boolean selectionMouseFlag = false;
+		private boolean itemChangeMouseFlag = false;
+	}
+	
 	private static JMenuBar menuBar;
+	private static ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechOptionPane.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechOptionPane.java	Wed Apr 25 17:09:09 2012 +0100
@@ -23,6 +23,8 @@
 import java.awt.Component;
 import java.awt.Frame;
 import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowAdapter;
@@ -33,6 +35,8 @@
 import java.util.ResourceBundle;
 import java.util.Set;
 
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JComponent;
 import javax.swing.JDialog;
@@ -53,16 +57,144 @@
 
 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.speech.Narrator;
 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
 import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
 
 /**
  * 
- * TheSpeechOptionPane provides one-line calls to display accessible dialog boxes. Input by the user as well 
- * as focused components are spoken out through text to speech synthesis performed by the {@link Narrator} instance. 
+ * An option panel made out of an {@code Object} being displayed and to buttons: one for accepting and another one for 
+ * cancelling the option.   
+ * Furthermore, this class provides one-line calls to display accessible dialog boxes. Input by the user as well 
+ * as focused components are spoken out through text to speech synthesis performed by a {@link Narrator} instance.
+ *   
  *  
  */
-public abstract class SpeechOptionPane {
+public class SpeechOptionPane {
+	
+	/**
+	 * Construct a new {@code SpeechOptionPane} with no title. The title is displayed at the top of the dialog
+	 * that is displayed after a call to {@code showDialog} 
+	 */
+	public SpeechOptionPane(){
+		this("");
+	}
+	
+	/**
+	 * Construct a new {@code SpeechOptionPane} with no title. The title is displayed at the top of the dialog
+	 * that is displayed after a call to {@code showDialog}
+	 * 
+	 * @param title the String to be displayed
+	 */
+	public SpeechOptionPane(String title){
+		this.title = title; 
+		okButton = new JButton("OK");
+		cancelButton = new JButton("Cancel");
+	}
+	
+	/**
+	 * Pops the a dialog holding this SpeechOptionPane 
+	 *  
+	 * @param parent the parent component of the dialog
+	 * @param the {@code Object} to display
+	 * @return an integer indicating the option selected by the user
+	 */
+	@SuppressWarnings("serial")
+	public int showDialog(Component parent,final Object message){
+		optPane = new JOptionPane();
+		optPane.setMessage(message);
+		/* Enter will entail a unique action, regardless the component that's focused */
+		optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "closeDialog");
+		optPane.getActionMap().put("closeDialog", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				okButton.doClick();
+			}
+		});
+		optPane.setMessageType(JOptionPane.PLAIN_MESSAGE);
+		Object[] options = {
+				okButton,
+				cancelButton
+		};
+		optPane.setOptions(options);
+		/* ctrl key will hush the TTS */
+		optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up");
+		optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction());
+		final JDialog dialog = optPane.createDialog(parent, title);
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		/* when either button is pressed, dialog is disposed and the button itself becomes the optPane.value */
+		ActionListener buttonListener = new ActionListener(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				onClose(dialog,message,(JButton)evt.getSource());
+			}
+		};
+		okButton.addActionListener(buttonListener);
+		cancelButton.addActionListener(buttonListener);
+
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		dialog.dispose();
+		if(okButton.equals(optPane.getValue())){
+			return OK_OPTION;
+		}else{
+			return CANCEL_OPTION;
+		}
+	}
+	
+	/**
+	 * Sets the string appearing at the top of the dialog where this option pane is displayed when {@code showDialog}
+	 * is called.
+	 * @param title
+	 */
+	public void setDialogTitle(String title){
+		this.title = title;
+	}
+	
+	/**
+	 * Returns the {@code JButton} that the user has to press (when the option pane is displayed after 
+	 * {@code showDialog} is called) in order to accept the option.
+	 *    
+	 * @return a reference to the internal {@code JButton} 
+	 */
+	public JButton getOkButton(){
+		return okButton;
+	}
+	
+	/**
+	 * Returns the {@code JButton} that the user has to press (when the option pane is displayed after 
+	 * {@code showDialog} is called) in order to reject the option.
+	 *    
+	 * @return a reference to the internal {@code JButton} 
+	 */
+	public JButton getCancelButton(){
+		return cancelButton;
+	}
+	
+	/**
+	 * This method is called just after the user pressed either button of the dialog displayed
+	 * after {@code showDialog} is called.
+	 * It assign a value to the return value and it frees the dialog resources.
+	 * It can be overwritten by subclasses but care should be taken of calling this class method via
+	 * {@code super} in order to properly close the dialog.   
+	 * 
+	 * @param dialog the dialog displayed after {@code showDialog} is called. 
+	 * @param message
+	 * @param source the button that triggered the closing of {@code dialog}
+	 */
+	protected void onClose(JDialog dialog,Object message, JButton source){
+		optPane.setValue(source);
+		dialog.dispose();
+	}
+	
+	private String title;
+	private JOptionPane optPane;
+	private JButton okButton;
+	private JButton cancelButton;
+	
+	
+	/* -------- STATIC METHODS ----------- */
 	
 	public static String showTextAreaDialog(Component parentComponent, String message, String initialSelectionValue){
 		JTextArea textArea = new JTextArea(NOTES_TEXT_AREA_ROW_SIZE,NOTES_TEXT_AREA_COL_SIZE);
@@ -254,8 +386,7 @@
 	}
 	
 	public static <T,V> int showProgressDialog(Component parentComponent, String message,final ProgressDialogWorker<T,V> worker, int millisToDecideToPopup){
-		JProgressBar progressBar = new JProgressBar();
-		progressBar.setIndeterminate(true);
+		JProgressBar progressBar = worker.bar;
 		Object displayObjects[] = {message, progressBar};
 		final JOptionPane optPane = new JOptionPane(displayObjects);
 		optPane.setOptionType(DEFAULT_OPTION);
@@ -363,17 +494,23 @@
 	
 	
 	public static abstract class ProgressDialogWorker<T,V> extends SwingWorker<T,V> {
+		public ProgressDialogWorker(){
+			bar = new JProgressBar();
+			bar.setIndeterminate(true);
+		}
+		
 		private void setDialog(JDialog dialog){
 			this.dialog = dialog;
 		}
 		
 		@Override
-	    protected void done() {
+	    protected void done() { //executed in EDT when the work is done
 			if(dialog != null)
 				dialog.dispose();
 		}
 		
 		private JDialog dialog;
+		protected JProgressBar bar;
 	}
 	
 	
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/TemplateEditor.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/TemplateEditor.java	Wed Apr 25 17:09:09 2012 +0100
@@ -23,16 +23,56 @@
 import java.util.Collection;
 
 /**
- * A Template editor is used to create new types of diagrams. Such prototypes diagrams will then be used 
- * to create diagram instances (the actual diagrams operated by the user) through clonation.    
- * when a diagram prototype is created, it's added in the File->new Diagram menu. 
+ * A template editor is used to create new types of diagrams. 
+ * 
+ * Template editors are run in the Event Dispatching Thread and can therefore make use of swimg components
+ * to prompt the user with choices about the diagram to be created. The diagram created by 
+ * a template editor is precisely a prototype.
+ * Such prototypes diagrams will then be used 
+ * to create new diagram instances (the actual diagrams operated by the user) through clonation.    
+ * When a diagram prototype is created, it's added in the File->new Diagram menu. 
  */
 public interface TemplateEditor {
+	
+	/**
+	 * Creates a new {@code Diagram}
+	 * 
+	 * @param frame the frame where the template editor is run
+	 * @param existingTemplates the names of already existing templates. The creation 
+	 * of a new {@code Diagram} with an already existing name must be prevented in order
+	 * to keep the consistency of the diagram templates. 
+	 * 
+	 * @return a new {@code Diagram} prototype
+	 */
 	public Diagram createNew(Frame frame, Collection<String> existingTemplates);
 	
+	/**
+	 * Edits an existing {@code Diagram} prototype.  
+	 *  
+	 * @param frame the frame where the template editor is run
+	 * @param existingTemplates the names of already existing templates. The creation 
+	 * of a new {@code Diagram} with an already existing name must be prevented in order
+	 * to keep the consistency of the diagram templates. 
+	 * @param diagram the diagram to edit 
+	 * @return a changed version of {@code diagram} 
+	 */
 	public Diagram edit(Frame frame, Collection<String> existingTemplates, Diagram diagram);
 	
+	/**
+	 * Templates editor methods are going to be called by the user via a menu item. This method
+	 * returns the label {@code createNew} menu item.
+	 * 
+	 * @return a label for the menu item which triggers the creation of a new template through this
+	 * template editor
+	 */
 	public String getLabelForNew();
 	
+	/**
+	 * Templates editor methods are going to be called by the user via a menu item. This method
+	 * returns the label {@code edit} menu item.
+	 * 
+	 * @return a label for the menu item which triggers the editing of a new template through this
+	 * template editor
+	 */
 	public String getLabelForEdit();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessFilter.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,159 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.gui.awareness;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.tree.TreePath;
+
+import uk.ac.qmul.eecs.ccmi.checkboxtree.CheckBoxTree;
+import uk.ac.qmul.eecs.ccmi.checkboxtree.CheckBoxTreeNode;
+import uk.ac.qmul.eecs.ccmi.checkboxtree.SetProperties;
+import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.speech.TreeSonifier;
+import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities;
+
+public abstract class AwarenessFilter {
+	
+	AwarenessFilter(String propertiesFilePath, String propertiesFileName) throws IOException{
+		if(propertiesFilePath == null)
+			throw new IOException(ResourceBundle.getBundle(AwarenessFilter.class.getName()).getString("error.no_properties_dir"));
+		properties = new SetProperties();
+		propertiesFile = new File(propertiesFilePath,propertiesFileName);
+		if(propertiesFile.exists())
+				properties.load(propertiesFile);
+	}
+	
+	/**
+	 * Returns the file name this instance of AwarenessFilter was build from. It should return an existing 
+	 * XML file name, which will be read through {@code getClass().getResourceAsStream}, representing the tree
+	 * displayed after calling {@code showDialog}.
+	 * 
+	 * @see {@link uk.ac.qmul.eecs.ccmi.checkboxtree.CheckBoxTree}
+	 * 
+	 * @return the name of the XML file  
+	 */
+	protected abstract String getXMLFileName();
+	
+	protected abstract String getDialogTitle();
+	
+	public abstract void saveProperties(Component parentComponent);
+	
+	public void showDialog(Component parent){
+		/* create and init components */
+		InputStream in = getClass().getResourceAsStream(getXMLFileName());
+		/* build the tree with a copy of the current properties, so that they won't be *
+		 * affected if the user press cancel be affected if the user                   */
+		CheckBoxTree tree = null;
+		synchronized(properties.getMonitor()){
+			tree = new CheckBoxTree(in,new SetProperties(properties));
+		}
+		try{
+			in.close();
+		}catch(IOException ioe){
+			ioe.printStackTrace();
+		}
+		new TreeSonifier(){
+			@Override
+			protected String currentPathSpeech(JTree tree) {
+				TreePath path =	tree.getSelectionPath();
+				CheckBoxTreeNode selectedPathTreeNode = (CheckBoxTreeNode)path.getLastPathComponent();
+				return selectedPathTreeNode.spokenText();
+			}
+			@Override
+			protected void space(JTree tree){
+				TreePath path =	tree.getSelectionPath();
+				CheckBoxTreeNode treeNode = (CheckBoxTreeNode)path.getLastPathComponent();
+				((CheckBoxTree)tree).toggleSelection(treeNode);
+				NarratorFactory.getInstance().speak(treeNode.spokenText());
+			}
+		}.sonify(tree);
+		
+		/* make the user aware that the dialog is opening */
+		NarratorFactory.getInstance().speak(getDialogTitle());
+		
+		SpeechOptionPane optionPane = new SpeechOptionPane(getDialogTitle());
+		JPanel panel = new JPanel(new GridBagLayout());
+		GridBagUtilities gridBag = new GridBagUtilities();
+		JScrollPane scrollPane = new JScrollPane(tree);
+		scrollPane.setPreferredSize(new Dimension(200,300));
+		panel.add(scrollPane,gridBag.all());
+		
+		int result = optionPane.showDialog(parent, panel);
+		if(result == SpeechOptionPane.CANCEL_OPTION)
+			return;
+		
+		synchronized(properties.getMonitor()){
+			configurationHasChanged = true;
+			properties.clear();
+			for(String property : tree.getProperties())
+				properties.add(property);
+		}
+	}
+	
+	/**
+	 * This method can be called to query the filter on whether the configuration has changed, 
+	 * that is {@code showDialog} has been called since the last time this method was called. 
+	 * Successive calls of this method will return false until the configuration dialog will be 
+	 * displayed again. This method is thread-safe and can be called by thread different from 
+	 * the Event Dispatching Thread, where {@code showDialog} should be called.   
+	 * 
+	 * @return true if the configuration has changed 
+	 */
+	public boolean configurationHasChanged(){
+		synchronized(properties.getMonitor()){
+			boolean toReturn = configurationHasChanged;
+			if(configurationHasChanged)
+				configurationHasChanged = false;
+			return toReturn;
+		}
+	}
+	
+	public void saveProperties(Component parentComponent, String comments) {
+		ResourceBundle resources = ResourceBundle.getBundle(AwarenessFilter.class.getName());
+		try {
+			if(!propertiesFile.getParentFile().exists())
+				throw new IOException(resources.getString("error.no_properties_dir"));
+				propertiesFile.createNewFile();
+				properties.store(propertiesFile, comments);
+		}catch (IOException ioe){
+			SpeechOptionPane.showMessageDialog(
+					parentComponent,
+					MessageFormat.format(resources.getString("error.write_file"), 
+							ioe.getLocalizedMessage())
+					);
+		}
+	}
+	
+	protected final SetProperties properties;
+	private boolean configurationHasChanged;
+	private File propertiesFile; 
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessFilter.properties	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,36 @@
+tree_root=Awareness Filter
+broadcast.properties.file_name=broadcast.txt
+broadcast.properties.comments=Awareness message broadcast configuration file. Generated automatically. DO NOT EDIT! 
+
+display.properties.file_name=display.txt
+display.properties.comments=Awareness message display configuration file. Generated automatically. DO NOT EDIT!
+
+error.no_properties_dir=Could not open the library directory ("ccmi_editor_data/libs")
+error.write_file=Error while saving awareness configuration: {0}
+
+dialog.display.title=Display Filter Dialog
+dialog.broadcast.title=Broadcast Filter Dialog 
+
+
+action.text.add_node=added node{0}
+action.text.add_edge=added edge{0}
+action.text.remove_node=removing node{0}
+action.text.remove_edge=removing edge{0}
+action.text.edit_node=editing node{0}
+action.text.edit_edge=editing edge{0}
+action.text.move_node=moving node{0}
+action.text.move_edge=moving edge{0}
+action.text.select_node=selected node{0}
+action.text.unselect_node=unselected node{0}
+
+action.text.add_node.verb=added node{0}
+action.text.add_edge.verb=added edge{0}
+action.text.remove_node.verb=is removing node{0}
+action.text.remove_edge.verb=is removing edge{0}
+action.text.edit_node.verb=is editing node{0}
+action.text.edit_edge.verb=is editing edge{0}
+action.text.move_node.verb=is moving node{0}
+action.text.move_edge.verb=is moving edge{0}
+action.text.select_node.verb=selected node{0}
+action.text.unselect_node.verb=unselected node{0}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanel.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,61 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.gui.awareness;
+
+import javax.swing.JSplitPane;
+
+/**
+ * The panel where awareness informations are displayed. The panel is split in two sub panel: the top 
+ * sub panel holds informations about the actions of other users, the bottom sub panel holds a list
+ * of the name of the users currently partaking the collaboration.  
+ *
+ */
+@SuppressWarnings("serial")
+public class AwarenessPanel extends JSplitPane {
+	/**
+	 * Creates a new instance of this class, bound to a diagram.    
+	 * @param diagramName the name of the diagram this panel is bound to.
+	 */
+	public AwarenessPanel(String diagramName){
+		super(VERTICAL_SPLIT,true);
+		usersPane = new AwarenessTextPane("user names panel");
+		recordsPane = new AwarenessTextPane("awareness panel");
+		setTopComponent(recordsPane);
+		setRightComponent(usersPane);
+		setResizeWeight(1.0);
+		setDividerLocation(0.4);
+		this.diagramName = diagramName;
+	}
+	
+	public AwarenessTextPane getUsersPane() {
+		return usersPane;
+	}
+
+	public AwarenessTextPane getRecordsPane() {
+		return recordsPane;
+	}
+	
+	public String getDiagramName(){
+		return diagramName;
+	}
+
+	private AwarenessTextPane usersPane;
+	private AwarenessTextPane recordsPane;
+	private String diagramName;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanelEditor.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,120 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.gui.awareness;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.Timer;
+
+import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage;
+
+public class AwarenessPanelEditor {
+	public AwarenessPanelEditor() {
+		awarenessPanels = Collections.synchronizedList(new ArrayList<AwarenessPanel>());
+	}
+
+	public void addAwarenessPanel(AwarenessPanel panel){
+		awarenessPanels.add(panel);
+	}
+	
+	public void removeAwarenessPanel(AwarenessPanel panel){
+		awarenessPanels.remove(panel);
+	}
+	
+	/**
+	 * Replaces a user's user name with a new one. 
+	 * 
+	 * @param diagramName the diagram the update has to be performed on
+	 * @param userNames a concatenation of the new user name and the old one. The
+	 * old user name can possibly be the empty string if the client has sent its 
+	 * user name for the first time (hence no old user name exists). 
+	 */
+	public void replaceUserName(String diagramName, String userNames){
+		AwarenessPanel panel = findPanel(diagramName);
+		if(panel == null)
+			return;
+		String [] names = userNames.split(AwarenessMessage.USERNAMES_SEPARATOR);// [0] = new name, [1] = old name
+		if(names.length == 2){
+			if(names[0].isEmpty()){ // if the new name is empty, then it's like just removing the old one 
+				panel.getUsersPane().remove(names[1]+'\n');
+				return;
+			}
+			panel.getUsersPane().remove(names[1]+'\n');
+		}
+		panel.getUsersPane().insert(names[0]+'\n');
+	}
+	
+	public void removeUserName(String diagramName, String userName){
+		AwarenessPanel panel = findPanel(diagramName);
+		if(panel != null)
+			panel.getUsersPane().remove(userName+'\n');
+	}
+	
+	public void addRecord(String diagramName, String record){
+		AwarenessPanel panel = findPanel(diagramName);
+		if(panel == null)
+			return;
+		panel.getRecordsPane().insert(record);
+	}
+	
+	public void addTimedRecord(final String diagramName, final String record){
+		addRecord(diagramName, record);
+		Timer timer = new Timer(TIMER_DELAY,new ActionListener(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				removeRecord(diagramName,record);
+			}
+		});
+		timer.setRepeats(false);
+		timer.start();
+	}
+	
+	public void removeRecord(String diagramName, String record){
+		AwarenessPanel panel = findPanel(diagramName);
+		if(panel == null)
+			return;
+		panel.getRecordsPane().remove(record);
+	}
+	
+	public void clearRecords(String diagramName){
+		AwarenessPanel panel = findPanel(diagramName);
+		if(panel == null)
+			return;
+		panel.getRecordsPane().clear();
+	}
+	
+	private AwarenessPanel findPanel(String diagramName){
+		// it's a synchronized collection, this will synchronize with add and remove
+		synchronized(awarenessPanels){
+			for(AwarenessPanel p : awarenessPanels){
+				if(p.getDiagramName().equals(diagramName)){
+					return p;
+				}
+			}
+		}
+		return null;
+	}
+	
+	private List<AwarenessPanel> awarenessPanels;
+	private static int TIMER_DELAY = 2000;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessTextPane.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,82 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package uk.ac.qmul.eecs.ccmi.gui.awareness;
+
+import java.awt.Component;
+import java.awt.event.KeyEvent;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JTextPane;
+import javax.swing.KeyStroke;
+import javax.swing.text.DefaultEditorKit;
+
+import uk.ac.qmul.eecs.ccmi.main.DiagramEditorApp;
+import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
+
+@SuppressWarnings("serial")
+public class AwarenessTextPane extends JTextPane {
+	public AwarenessTextPane(String accessibleName){
+		records = new LinkedList<String>();
+		addKeyListener(SpeechUtilities.getSpeechKeyListener(false,true));
+		/* prevents getText() from automatically turn all the \n to \r\n */
+		getDocument().putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
+		getAccessibleContext().setAccessibleName(accessibleName);
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,0), "none");	
+		SpeechUtilities.changeTabListener(this,DiagramEditorApp.getFrame());
+	}
+	
+	public void insert(String record){
+		records.add(0, record);
+		update();
+	}
+	
+	public void remove(String record){
+		records.remove(record);
+		update();
+	}
+	
+	public void clear(){
+		records.clear();
+		update();
+	}
+	
+	protected void processKeyEvent(KeyEvent e){
+		if(e.getKeyCode() != KeyEvent.VK_UP &&
+			e.getKeyCode() != KeyEvent.VK_DOWN &&
+			 e.getKeyCode() != KeyEvent.VK_RIGHT &&
+			  e.getKeyCode() != KeyEvent.VK_LEFT  &&
+			   e.getKeyCode() != KeyEvent.VK_TAB)
+			return;
+		super.processKeyEvent(new KeyEvent((Component)e.getSource(),e.getID(),
+					e.getWhen(),0,e.getKeyCode(),e.getKeyChar()));
+	}
+	
+	private void update(){
+		StringBuilder builder = new StringBuilder();
+		for(String r : records){
+			builder.append(r);
+		}
+		selectAll();
+		replaceSelection(builder.toString());
+	}
+	
+	private List<String> records;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/BroadcastFilter.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,158 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.gui.awareness;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.util.ResourceBundle;
+
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+public class BroadcastFilter extends AwarenessFilter  {
+	public static BroadcastFilter getInstance(){
+		return broadcastFilter;
+	}
+	
+	public static BroadcastFilter createInstance() throws IOException {
+		broadcastFilter = new BroadcastFilter();
+		return broadcastFilter;
+	}
+	
+	private BroadcastFilter() throws IOException{
+		super(PreferencesService.getInstance().get("dir.libs", null),
+			ResourceBundle.getBundle(AwarenessFilter.class.getName()).getString("broadcast.properties.file_name"));
+	}
+	
+	@Override
+	protected String getXMLFileName(){
+		return "BroadcastFilterTree.xml";
+	}
+	
+	@Override
+	protected String getDialogTitle(){
+		return ResourceBundle.getBundle(AwarenessFilter.class.getName()).getString("dialog.broadcast.title");
+	}
+	
+	@Override
+	public void saveProperties(Component parentComponent){
+		ResourceBundle resources = ResourceBundle.getBundle(AwarenessFilter.class.getName());
+		super.saveProperties(
+				parentComponent, 
+				resources.getString("broadcast.properties.comments")		
+			);
+	}
+	
+	public boolean accept(DiagramEventActionSource action){
+		/* don't accept if the user didn't select the modality this action was generated from */
+		switch(action.type){
+		case TREE : if(!properties.contains("Broadcast Filter.Where (Source).Audio View")) return false;
+			break;
+		case GRPH : if(!properties.contains("Broadcast Filter.Where (Source).Graphic View")) return false;
+			break;
+		case HAPT : if(!properties.contains("Broadcast Filter.Where (Source).Haptic View")) return false;
+			break;
+		}
+		
+		switch(action.getCmd()){
+			case INSERT_EDGE : 
+			case SELECT_NODE_FOR_EDGE_CREATION :	
+			case UNSELECT_NODE_FOR_EDGE_CREATION : if(!properties.contains("Broadcast Filter.What (Action).Edge Add")) return false;
+				break;
+			case REMOVE_EDGE : if(!properties.contains("Broadcast Filter.What (Action).Edge Remove")) return false;
+				break;
+			case INSERT_NODE : if(!properties.contains("Broadcast Filter.What (Action).Node Add")) return false;
+				break;
+			case REMOVE_NODE : if(!properties.contains("Broadcast Filter.What (Action).Node Remove")) return false;
+				break;
+			case SET_NODE_NAME : 
+			case SET_PROPERTY : 
+			case SET_PROPERTIES :
+			case ADD_PROPERTY :
+			case REMOVE_PROPERTY :
+			case SET_MODIFIERS : 
+				if(!properties.contains("Broadcast Filter.What (Action).Node Edited")) return false;
+				break;
+			case SET_ENDDESCRIPTION : 
+			case SET_EDGE_NAME : 
+			case SET_ENDLABEL : 
+				if(!properties.contains("Broadcast Filter.What (Action).Edge Edited")) return false;
+				break;
+			case STOP_NODE_MOVE :
+			case TRANSLATE_NODE : 
+				if(!properties.contains("Broadcast Filter.What (Action).Node Moved")) return false;
+				break;
+			case TRANSLATE_EDGE :
+			case BEND : 
+			case STOP_EDGE_MOVE : 
+				if(!properties.contains("Broadcast Filter.What (Action).Edge Moved")) return false;
+				break;
+			default : return false; // if it's none of these commands(e.g. set notes), then don't accept the action 
+		}
+		return true;
+	}
+	
+	public DiagramEventActionSource process(DiagramEventActionSource action){
+		/* delete the timestamp if the user decided not to broadcast it */
+		if(properties.contains("Broadcast Filter.When.Active History"))
+			action.setTimestamp(System.currentTimeMillis());
+		else
+			action.setTimestamp(0);
+		
+		/* delete the user id if the user decided not to broadcast it */
+		if(!properties.contains("Broadcast Filter.Who.User Id"))
+			action.setUserName("");
+		
+		/* delete the element id if the user decided not to broadcast it */
+		switch(action.getCmd()){
+		case INSERT_NODE : 
+		case REMOVE_NODE : 
+		case TRANSLATE_NODE : 
+		case SET_NODE_NAME :
+		case STOP_NODE_MOVE :
+		case SET_PROPERTY : 
+		case SET_PROPERTIES :
+		case ADD_PROPERTY : 
+		case REMOVE_PROPERTY : 
+		case SET_MODIFIERS :
+			if(!properties.contains("Broadcast Filter.What (Object).Which Node"))
+				action.setElementID(Node.NO_ID);
+			break;
+		case INSERT_EDGE : 
+		case SELECT_NODE_FOR_EDGE_CREATION :
+		case UNSELECT_NODE_FOR_EDGE_CREATION :
+		case REMOVE_EDGE : 
+		case SET_ENDDESCRIPTION : 
+		case SET_ENDLABEL : 
+		case SET_EDGE_NAME : 
+		case TRANSLATE_EDGE :
+		case BEND : 
+		case STOP_EDGE_MOVE :
+			if(!properties.contains("Broadcast Filter.What (Object).Which Edge"))
+				action.setElementID(Edge.NO_ID);
+			break;
+		}
+		return action;
+	}
+	
+	private static BroadcastFilter broadcastFilter;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/BroadcastFilterTree.xml	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<unselectable value="Broadcast Filter">
+
+	<selectable value="Who">
+		<selectable value="User Id"/>
+	</selectable>
+	
+	<selectable value="What (Action)">
+		<selectable value="Node Add"/>
+		<selectable value="Node Remove"/>
+		<selectable value="Node Edited"/>
+		<selectable value="Node Moved"/>
+		<selectable value="Edge Add"/>
+		<selectable value="Edge Remove"/>
+		<selectable value="Edge Edited"/>
+		<selectable value="Edge Moved"/>
+	</selectable>
+		
+	<selectable value="What (Object)">
+		<selectable value="Which Node"/>
+		<selectable value="Which Edge"/>
+	</selectable>
+	
+	<selectable value="Where (Object)">
+		<selectable value="Edge Coordinates" />
+	</selectable>
+	
+	<selectable value="Where (Source)">
+		<selectable value="Audio View"/>
+		<selectable value="Haptic View"/>
+		<selectable value="Graphic View"/>
+	</selectable>
+	
+	<selectable value="When">
+		<selectable value="Active History"/>
+	</selectable>
+	
+</unselectable>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/DisplayFilter.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,159 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.gui.awareness;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
+import uk.ac.qmul.eecs.ccmi.network.Command.Name;
+import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+
+public class DisplayFilter extends AwarenessFilter {
+	public static DisplayFilter createInstance() throws IOException{
+		if(displayFilter == null)
+			displayFilter = new DisplayFilter();
+		return displayFilter;
+	}
+	
+	public static DisplayFilter getInstance(){
+		return displayFilter;
+	}
+	
+	private DisplayFilter() throws IOException{
+		super(PreferencesService.getInstance().get("dir.libs", null),
+			ResourceBundle.getBundle(AwarenessFilter.class.getName()).getString("display.properties.file_name"));
+		resources = ResourceBundle.getBundle(AwarenessFilter.class.getName());
+	}
+
+	@Override
+	protected String getXMLFileName() {
+		return "DisplayFilterTree.xml";
+	}
+	
+	@Override
+	protected String getDialogTitle(){
+		return resources.getString("dialog.display.title");
+	}
+	
+	@Override
+	public void saveProperties(Component parentComponent) {
+		super.saveProperties(parentComponent, resources.getString("display.properties.comments"));
+	}
+	
+	public String processForSpeech(DiagramEventActionSource actionSource){
+		return process(actionSource,"Speech");
+	}
+	
+	public String processForText(DiagramEventActionSource actionSource){
+		return process(actionSource,"Text");
+	}
+
+	private String process(DiagramEventActionSource actionSource,String propertiesAppendix){
+		boolean includeUser = properties.contains("Display Filter.Who."+propertiesAppendix);
+		boolean includeAction = properties.contains("Display Filter.What (Action)."+propertiesAppendix);
+		boolean includeObject = properties.contains("Display Filter.What (Object)."+propertiesAppendix);
+		
+		// match with server configuration
+		includeUser = (includeUser && (!actionSource.getUserName().isEmpty()));
+		includeObject = (includeObject && (actionSource.getElementID() != DiagramElement.NO_ID));
+
+		/* build up the sentence to be displayed to the user */
+		StringBuilder builder = new StringBuilder();
+
+		if(includeUser){
+			builder.append(actionSource.getUserName()).append(' ');
+		}
+
+		/* if the object is included by the filter it will be * 
+		 * passed to the message format of the action string  */
+		String objectString = "";
+		if(includeObject){
+			objectString = actionSource.getElementName();
+		}
+		
+		if(includeAction){
+			builder.append(getActionString(actionSource.getCmd()," " + objectString,includeUser));
+		}else if(!objectString.isEmpty()){
+			builder.append(objectString);
+		}
+		
+		if(!(builder.length() == 0))
+			builder.append('\n');
+		return builder.toString();
+	}
+	
+	private String getActionString(Name cmd, String objectString, boolean includeVerb) {
+		String verb = includeVerb ? ".verb" : "";
+		switch(cmd){
+		case INSERT_EDGE :
+			 return MessageFormat.format(resources.getString("action.text.add_edge"+verb),
+					 objectString);
+		case INSERT_NODE :
+			 return MessageFormat.format(resources.getString("action.text.add_node"+verb),
+					 objectString);
+		case REMOVE_NODE :
+			return MessageFormat.format(resources.getString("action.text.remove_node"+verb),
+					 objectString);
+		case REMOVE_EDGE :
+			return MessageFormat.format(resources.getString("action.text.remove_edge"+verb),
+					 objectString);
+		case SET_NODE_NAME :
+		case SET_PROPERTY :
+		case SET_PROPERTIES :
+		case CLEAR_PROPERTIES :
+		case SET_NOTES :
+		case ADD_PROPERTY :
+		case REMOVE_PROPERTY :
+		case SET_MODIFIERS :
+			return MessageFormat.format(resources.getString("action.text.edit_node"+verb),
+					 objectString);
+		case SET_ENDDESCRIPTION :
+		case SET_EDGE_NAME :
+		case SET_ENDLABEL :
+			return MessageFormat.format(resources.getString("action.text.edit_edge"+verb),
+					 objectString);
+		case TRANSLATE_NODE :
+		case STOP_NODE_MOVE :
+			return MessageFormat.format(resources.getString("action.text.move_node"+verb),
+					 objectString);
+		case TRANSLATE_EDGE :
+		case BEND :
+		case STOP_EDGE_MOVE :
+			return MessageFormat.format(resources.getString("action.text.move_edge"+verb),
+					 objectString);
+		case SELECT_NODE_FOR_EDGE_CREATION :
+			return MessageFormat.format(resources.getString("action.text.select_node"+verb),
+					 objectString);
+		case UNSELECT_NODE_FOR_EDGE_CREATION :
+			return MessageFormat.format(resources.getString("action.text.unselect_node"+verb),
+					 objectString);
+		default : return ""; 
+		}
+	}
+	
+	private static DisplayFilter displayFilter;
+	ResourceBundle resources;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/DisplayFilterTree.xml	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<unselectable value="Display Filter">
+	<selectable value="Who">
+		<selectable value="Text"/>
+		<selectable value="Color"/>
+		<selectable value="Speech"/>
+	</selectable>
+	
+	<selectable value="What (Action)">
+		<selectable value="Text"/>
+		<selectable value="Speech"/>
+	</selectable> 
+
+	<selectable value="What (Object)"> 
+		<selectable value="Text"/>
+		<selectable value="Color"/>
+		<selectable value="Speech"/>
+		<selectable value="Non speech sound"/>
+	</selectable>
+</unselectable>
\ No newline at end of file
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTree.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTree.java	Wed Apr 25 17:09:09 2012 +0100
@@ -25,6 +25,7 @@
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
 import java.io.File;
+import java.io.IOException;
 import java.util.LinkedList;
 import java.util.ResourceBundle;
 
@@ -76,15 +77,19 @@
 	public void setSelectionPath(TreePath path){
 		super.setSelectionPath(path);
 		scrollPathToVisible(path);
+		getSelectionPath();
 	}
-
+	
 	public void setSelectionPath(File file){
 		if(file == null)
 			return;
 		
-		/* we need the absolute path as the tree has to be expanded from the root to file *
-		 * and we need all the directory names                                            */ 
-		file = file.getAbsoluteFile();
+		try {
+			file = file.getCanonicalFile();
+		} catch (IOException e) {
+			setSelectionPath(new TreePath(getModel().getRoot()));
+			return;
+		}
 		/* make a file path: a list of file's each one representing a directory of file's path */
 		LinkedList<File> filePath = new LinkedList<File>();
 		filePath.add(file);
@@ -120,7 +125,10 @@
 		treeSelectionListenerGateOpen = false;
 		((DefaultTreeModel)getModel()).setRoot(FileSystemTreeNode.getRootNode(filter));
 		treeSelectionListenerGateOpen = true;
-		setSelectionPath(file);
+		if(file == null)
+			setSelectionPath(new TreePath(getModel().getRoot()));
+		else
+			setSelectionPath(file);
 	}
 
 	@Override
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.java	Wed Apr 25 17:09:09 2012 +0100
@@ -21,31 +21,22 @@
 
 import java.awt.Component;
 import java.awt.GridBagLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.InputEvent;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
-import java.awt.event.KeyEvent;
 import java.io.File;
 import java.text.MessageFormat;
 import java.util.ResourceBundle;
 
-import javax.swing.AbstractAction;
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.JButton;
 import javax.swing.JComboBox;
-import javax.swing.JComponent;
 import javax.swing.JDialog;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
-import javax.swing.KeyStroke;
 import javax.swing.filechooser.FileFilter;
 
-import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
-import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
 import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
 import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities;
@@ -61,22 +52,6 @@
 		super(new GridBagLayout());
 		initComponents();
 		addComponents();
-		/* Enter will entail a unique action, regardless the component that's focused */
-		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "closeDialog");
-		getActionMap().put("closeDialog", new AbstractAction(){
-			@Override
-			public void actionPerformed(ActionEvent evt) {
-				FileSystemTreeNode treeNode = (FileSystemTreeNode)tree.getSelectionPath().getLastPathComponent();
-				if(treeNode.getFile().isDirectory()){
-					SoundFactory.getInstance().play(SoundEvent.ERROR);
-					return;
-				}
-				if(isOpenFileDialog)
-					openButton.doClick();
-				else
-					saveButton.doClick();
-			}
-		});
 	}
 
 	private void addComponents() {
@@ -104,27 +79,6 @@
 		fileTypeComboBox = new JComboBox(items);
 		fileTypeLabel.setLabelFor(fileTypeComboBox);
 
-		/* open, save cancel buttons, when pressed, will close the dialog and assign a value to the option pane */
-		openButton = new JButton(resources.getString("open_button.label"));
-		saveButton = new JButton(resources.getString("save_button.label"));
-		cancelButton = new JButton(resources.getString("cancel_button.label"));
-		ActionListener buttonListener = new ActionListener(){
-			@Override
-			public void actionPerformed(ActionEvent evt) {
-				if(evt.getSource().equals(openButton)||evt.getSource().equals(saveButton)){
-					if(fileNameTextField.getText().isEmpty()){
-						NarratorFactory.getInstance().speak(resources.getString("dialog.error.no_file_name"));
-						return;
-					}
-				}
-				optPane.setValue(evt.getSource());
-				dialog.dispose();
-			}
-		};
-		openButton.addActionListener(buttonListener);
-		cancelButton.addActionListener(buttonListener);
-		saveButton.addActionListener(buttonListener);
-		
 		/* set up the listener binding the tree with the file name and file type components */
 		tree = new FileSystemTree((FileFilter)fileTypeComboBox.getSelectedItem());
 		tree.addTreeSelectionListener(fileNameTextField);
@@ -180,48 +134,52 @@
 
 	@Override
 	public int showOpenDialog(Component parent){
-		isOpenFileDialog = true;
 		FileSystemTreeNode treeNode = (FileSystemTreeNode)tree.getSelectionPath().getLastPathComponent();
 		NarratorFactory.getInstance().speak(
 				MessageFormat.format(
 						resources.getString("dialog.open.message"),
 						treeNode.spokenText()));
-		return showDialog(parent);
+		return showDialog(parent,true);
 	}
 
 	@Override
 	public int showSaveDialog(Component parent){
-		isOpenFileDialog = false;
 		FileSystemTreeNode treeNode = (FileSystemTreeNode)tree.getSelectionPath().getLastPathComponent();
 		NarratorFactory.getInstance().speak(
 				MessageFormat.format(
 						resources.getString("dialog.save.message"),
 						treeNode.spokenText()));
-		return showDialog(parent);
+		return showDialog(parent,false);
 	}
 
-	private int showDialog(Component parent){
-		optPane = new JOptionPane();
-		optPane.setMessage(this);
-		optPane.setMessageType(JOptionPane.PLAIN_MESSAGE);
-		Object[] options = {
-				isOpenFileDialog ? openButton : saveButton,
-				cancelButton
+	private int showDialog(Component parent, boolean isOpenFileDialog){
+		/* overrides on close so that, before closing the dialog it checks that a file name has actually               * 
+		 * been entered by the user. If not, the dialog won't close and a error will be notified through the narrator  */
+		SpeechOptionPane optionPane = new SpeechOptionPane(resources.getString("dialog.open.title")){
+			@Override
+			protected void onClose(JDialog dialog,Object message, JButton source){
+				if(source.equals(getOkButton())){
+					if(fileNameTextField.getText().isEmpty()){
+						NarratorFactory.getInstance().speak(resources.getString("dialog.error.no_file_name"));
+						return;
+					}
+				}
+				super.onClose(dialog, message, source);
+			}
 		};
-		optPane.setOptions(options);
-		/* ctrl key will hush the TTS */
-		optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up");
-		optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction());
-		dialog = optPane.createDialog(parent, resources.getString("dialog.open.title"));
-		SpeechUtilities.changeTabListener(optPane,dialog);
-
-		/* add the speech listener just before showing up, otherwise it will talk when filters are added/removed */
+		
+		if(isOpenFileDialog)
+			optionPane.getOkButton().setText(resources.getString("open_button.label"));
+		else
+			optionPane.getOkButton().setText(resources.getString("save_button.label"));
+		optionPane.getCancelButton().setText(resources.getString("cancel_button.label"));
+		
+		/* add the speech listener just before showing up and then remove it, otherwise it will talk when filters are added/removed */
 		fileTypeComboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
-		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
-		dialog.setVisible(true);
-		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		int result = optionPane.showDialog(parent, this);
 		fileTypeComboBox.removeItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
-		if((isOpenFileDialog && openButton.equals(optPane.getValue()))||(!isOpenFileDialog && saveButton.equals(optPane.getValue()))){
+		
+		if(result == SpeechOptionPane.OK_OPTION){
 			FileSystemTreeNode treeNode = (FileSystemTreeNode)tree.getSelectionPath().getLastPathComponent();
 			/* user has made his choice. The returned file will be the directory selected in the tree                    * 
 			 * or the parent directory of the selected file if a file is selected + the string entered in the JTextField */
@@ -245,12 +203,6 @@
 	private FileSystemTree tree;
 	private File selectedFile;
 	private File currentDir;	
-	private JButton openButton;
-	private JButton saveButton;
-	private JButton cancelButton;
-	private JOptionPane optPane;
-	private JDialog dialog;
-	boolean isOpenFileDialog;
 }
 
 class AllFileFilter extends FileFilter {
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.properties	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.properties	Wed Apr 25 17:09:09 2012 +0100
@@ -3,14 +3,14 @@
 file={0}
 
 tree_root.label=File System
-tree.accessible_name=File System
+tree.accessible_name=File System Tree
 
 open_button.label=Open
 cancel_button.label=Cancel
 save_button.label=Save
 
-dialog.open.title=Open File
-dialog.save.title=Save File
+dialog.open.title=Open File Dialog
+dialog.save.title=Save File Dialog
 dialog.open.message=Open File dialog. {0} selected
 dialog.save.message=Save File dialog. {0} selected
 dialog.file_chooser.file_name=File Name: 
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java	Wed Apr 25 17:09:09 2012 +0100
@@ -52,9 +52,10 @@
 import org.xml.sax.SAXException;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
 import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.gui.Edge;
 import uk.ac.qmul.eecs.ccmi.gui.Node;
 import uk.ac.qmul.eecs.ccmi.utils.CharEscaper;
@@ -202,10 +203,10 @@
         
         /* store notes */
         Element notesTag = doc.createElement(NOTES);
-        DiagramModelTreeNode treeRoot = (DiagramModelTreeNode)diagram.getTreeModel().getRoot();
+        DiagramTreeNode treeRoot = (DiagramTreeNode)diagram.getTreeModel().getRoot();
         for( @SuppressWarnings("unchecked")
-		Enumeration<DiagramModelTreeNode> enumeration = treeRoot.depthFirstEnumeration(); enumeration.hasMoreElements();){
-        	DiagramModelTreeNode treeNode = enumeration.nextElement();
+		Enumeration<DiagramTreeNode> enumeration = treeRoot.depthFirstEnumeration(); enumeration.hasMoreElements();){
+        	DiagramTreeNode treeNode = enumeration.nextElement();
         	if(!treeNode.getNotes().isEmpty()){
         		Element noteTag = doc.createElement(NOTE);
         		Element treeNodeTag = doc.createElement(TREE_NODE);
@@ -322,6 +323,7 @@
 	    CollectionModel<Node,Edge> collectionModel = diagram.getCollectionModel();
 	    TreeModel<Node,Edge> treeModel = diagram.getTreeModel();
 	    
+	    /* a map linking node ids in the XML file to the actual Node object they represent */
 	    Map<String,Node> nodesId = new LinkedHashMap<String,Node>();
 	    
 	    if(doc.getElementsByTagName(COMPONENTS).item(0) == null)
@@ -360,7 +362,7 @@
 			}catch(NumberFormatException nfe){
 				throw new IOException(resources.getString("dialog.error.malformed_file"),nfe);
 			}
-			collectionModel.insert(node);
+			collectionModel.insert(node,DiagramEventSource.PERS);
 			try{
 				node.decode(doc, nodeTag);
 			}catch(IOException ioe){ // just give a message to the exception 
@@ -397,7 +399,7 @@
 	    	}catch(IOException ioe){
 	    		throw new IOException(resources.getString("dialog.error.malformed_file"),ioe);
 	    	}
-	    	collectionModel.insert(edge);
+	    	collectionModel.insert(edge,DiagramEventSource.PERS);
 	    }
 	    
 	    /* retrieve bookmarks */
@@ -408,8 +410,8 @@
 	    	if(key.isEmpty()) 
 	    		throw new IOException(resources.getString("dialog.error.malformed_file"));
 	    	String path = bookmarkTag.getTextContent();
-	    	DiagramModelTreeNode treeNode = getTreeNodeFromString(treeModel,path);
-	    	treeModel.putBookmark(key, treeNode);
+	    	DiagramTreeNode treeNode = getTreeNodeFromString(treeModel,path);
+	    	treeModel.putBookmark(key, treeNode,DiagramEventSource.PERS);
 	    }
 	    
 	    /* retrieve notes */
@@ -424,8 +426,8 @@
 	    		throw new IOException(resources.getString("dialog.error.malformed_file"));
 	    	Element contentTag = (Element)noteTag.getElementsByTagName(CONTENT).item(0);
 	    	String content = CharEscaper.restoreNewline(contentTag.getTextContent());
-	    	DiagramModelTreeNode treeNode = getTreeNodeFromString(treeModel,path);
-	    	treeModel.setNotes(treeNode,content);
+	    	DiagramTreeNode treeNode = getTreeNodeFromString(treeModel,path);
+	    	treeModel.setNotes(treeNode,content,DiagramEventSource.PERS);
 	    }
 	    
 	    /* normally nodes and edges should be saved in order, this is to prevent *
@@ -492,7 +494,7 @@
 	    return eList;
 	}
 	
-	private static String getTreeNodeAsString(DiagramModelTreeNode treeNode){
+	private static String getTreeNodeAsString(DiagramTreeNode treeNode){
 		TreeNode[] path = treeNode.getPath();
 		StringBuilder builder = new StringBuilder();
 		for(int i=0;i<path.length-1; i++)
@@ -503,15 +505,15 @@
 		
 	}
 	
-	private static DiagramModelTreeNode getTreeNodeFromString(TreeModel<Node,Edge> model, String path) throws IOException{
-		DiagramModelTreeNode treeNode = (DiagramModelTreeNode)model.getRoot();
+	private static DiagramTreeNode getTreeNodeFromString(TreeModel<Node,Edge> model, String path) throws IOException{
+		DiagramTreeNode treeNode = (DiagramTreeNode)model.getRoot();
 		if(ROOT_AS_STRING.equals(path))
 			return treeNode;
 		String[] nodesAsString = path.split(" ");
 		
 		try {
 			for(String nodeAsString : nodesAsString)
-				treeNode = (DiagramModelTreeNode) treeNode.getChildAt(Integer.parseInt(nodeAsString));
+				treeNode = (DiagramTreeNode) treeNode.getChildAt(Integer.parseInt(nodeAsString));
 		}catch(Exception e){
 			throw new IOException(e);
 		}
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/DummyHaptics.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/DummyHaptics.java	Wed Apr 25 17:09:09 2012 +0100
@@ -34,39 +34,42 @@
 	public int init(int width, int height) { return 0;}
 
 	@Override
-	public synchronized void addNode(double x, double y, int diagramId) {}
+	public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName) {}
 
 	@Override
-	public synchronized void removeNode(int diagramId) {}
+	public synchronized void removeNode(int nodeHashCode, String diagramName) {}
 
 	@Override
-	public synchronized void removeEdge(int diagramId){}
+	public synchronized void removeEdge(int nodeHashCode, String diagramName){}
 
 	@Override
 	public synchronized void dispose() {}
 
 	@Override
-	public void addNewDiagram(int id, boolean switchAfter) {}
+	public void addNewDiagram(String diagramName, boolean switchAfter) {}
 
 	@Override
-	public synchronized void switchDiagram(int id) {	}
+	public synchronized void switchDiagram(String diagramName) {	}
 
 	@Override
-	public synchronized void removeDiagram(int idToRemove, Integer idNext) {}
+	public synchronized void removeDiagram(String diagramNameToRemove, String diagramNameNext) {}
 
 	@Override
-	public synchronized void moveNode(double x, double y, int diagramId) {}
+	public synchronized void moveNode(double x, double y, int nodeHashCode, String diagramName) {}
 
 	@Override
-	public synchronized void addEdge(int diagramId, double[] xs, double[] ys,
-			BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine) {}
+	public synchronized void addEdge(int nodeHashCode, double[] xs, double[] ys,
+			BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine, String diagramName) {}
 
 	@Override
-	public synchronized void updateEdge(int diagramId, double[] xs,
-			double[] ys, BitSet[] adjMatrix, int nodeStart, Line2D attractLine) {}
+	public synchronized void updateEdge(int nodeHashCode, double[] xs,
+			double[] ys, BitSet[] adjMatrix, int nodeStart, Line2D attractLine, String diagramName) {}
 
 	@Override
-	public synchronized void attractTo(int diagramId) {}
+	public synchronized void attractTo(int elementHashCode) {}
+	
+	@Override
+	public void pickUp(int elementHashCode){}
 	
 	@Override
 	public boolean isAlive(){
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListenerCommand.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListenerCommand.java	Wed Apr 25 17:09:09 2012 +0100
@@ -32,6 +32,7 @@
 	MOVE,
 	INFO,
 	NONE,
+	PICK_UP,
 	ERROR;
 	
 	public static HapticListenerCommand fromChar(char c){
@@ -44,6 +45,7 @@
 		case 'u' : return UNSELECT;
 		case 'g' : return PLAY_SOUND;
 		case 'e' : return ERROR;
+		case 'c' : return PICK_UP;
 		default : return NONE;
 		}
 	}
@@ -59,7 +61,6 @@
 			switch(i){
 			case 0 : return MAGNET_OFF;
 			case 1 : return MAGNET_ON;
-			case 2 : return HOOK_ON;
 			case 3 : return DRAG;
 			default : return NONE;
 			}
Binary file java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.dll has changed
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java	Wed Apr 25 17:09:09 2012 +0100
@@ -27,31 +27,33 @@
 
 	public int init(int width, int height) throws IOException;
 
-	public void addNewDiagram(int id, boolean switchAfter);
+	public void addNewDiagram(String diagramName, boolean switchAfter);
 
-	public void switchDiagram(int id);
+	public void switchDiagram(String diagramName);
 
-	public void removeDiagram(int idToRemove, Integer idNext);
+	public void removeDiagram(String diagramNameToRemove, String diagramNameOfNext);
 
-	public void addNode(double x, double y, int diagramId);
+	public void addNode(double x, double y, int nodeHashCode, String diagramName);
 
-	public void removeNode(int diagramId);
+	public void removeNode(int nodeHashCode, String diagramName);
 
-	public void moveNode(double x, double y, int diagramId);
+	public void moveNode(double x, double y, int nodeHashCode, String diagramName);
 
-	public void addEdge(int diagramId, double[] xs, double[] ys,
+	public void addEdge(int edgeHashCode, double[] xs, double[] ys,
 			BitSet[] adjMatrix, int nodeStart, int stipplePattern,
-			Line2D attractLine);
+			Line2D attractLine, String diagramName);
 
-	public void updateEdge(int diagramId, double[] xs, double[] ys,
-			BitSet[] adjMatrix, int nodeStart, Line2D attractLine);
+	public void updateEdge(int edgeHashCode, double[] xs, double[] ys,
+			BitSet[] adjMatrix, int nodeStart, Line2D attractLine, String diagramName);
 
-	public void removeEdge(int diagramId);
+	public void removeEdge(int edgeHashCode, String diagramName);
 
-	public void attractTo(int diagramId);
+	public void attractTo(int elementHashCode);
+	
+	public void pickUp(int elementHashCode);
 	
 	public boolean isAlive();
 
 	public void dispose();
-
+	
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java	Wed Apr 25 17:09:09 2012 +0100
@@ -27,10 +27,18 @@
  *
  */
 public class HapticsFactory {
+	/**
+	 * Creates a new instance of {@code Haptics}. If an Omni Haptic can be successfully initialised
+	 * {@code Haptics} will handle the device, otherwise all the calls to the object will have no effect.
+	 *  
+	 * @param listener an haptic commands listener to link to the {@code Haptics} instance.
+	 */
 	public static void createInstance(HapticListener listener) {
 		if(hapticsInstance != null)
 			throw new IllegalStateException("create instance must be called once only");
-		hapticsInstance = new DummyHaptics();
+		hapticsInstance = OmniHaptics.createInstance(listener);
+		if(hapticsInstance == null)
+			hapticsInstance = new DummyHaptics();
 	}
 	
 	public static Haptics getInstance(){
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/Node.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Node.java	Wed Apr 25 17:09:09 2012 +0100
@@ -46,5 +46,4 @@
 	public int diagramId; // not shared with the haptic thread
 	public int hapticId;
 	public ArrayList<Edge> edges;
-	
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,431 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+
+package uk.ac.qmul.eecs.ccmi.haptics;
+
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.geom.Line2D;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.ListIterator;
+
+import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter;
+import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+/*
+ *
+ * The implementation of the Haptics interface which uses Sensable®
+ * PHANTOM Omni® haptic device.
+ *
+ */
+class OmniHaptics extends Thread implements Haptics {
+	
+	static Haptics createInstance(HapticListener listener) {
+		if(listener == null)
+			throw new IllegalArgumentException("listener cannot be null");
+		
+		if(OsDetector.isWindows()){
+			/* try to load .dll. First copy it in the home/ccmi_editor_data/lib directory  */
+			URL url = OmniHaptics.class.getResource("Haptics.dll");
+			ResourceFileWriter fileWriter = new ResourceFileWriter(url);
+			fileWriter.writeToDisk(
+					PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),
+					"Haptics.dll");
+			String path = fileWriter.getFilePath();
+			if(path == null)
+				return null;
+			try{
+				System.load( path );
+			}catch(UnsatisfiedLinkError e){
+				return null; 
+			}
+		}else{
+			return null;
+		}
+		
+		OmniHaptics omniHaptics = new OmniHaptics("Haptics");
+		omniHaptics.hapticListener = listener;
+		/* start up the listener which immediately stops, waiting for commands */
+		if(!omniHaptics.hapticListener.isAlive())
+			omniHaptics.hapticListener.start();
+		/* start up the haptics thread which issues commands from the java to the c++ thread */
+		omniHaptics.start();
+		/* wait for the haptics thread (now running native code) to initialize (need to know if initialization is successful) */
+		synchronized(omniHaptics){
+			try {
+				omniHaptics.wait();
+			}catch (InterruptedException ie) {
+				throw new RuntimeException(ie); // must never happen 
+			}
+		}
+		if(omniHaptics.hapticInitFailed){
+				/* the initialization has failed, the haptic thread is about to die */
+				while(!omniHaptics.hapticListener.isInterrupted()){
+					omniHaptics.hapticListener.interrupt();
+				}
+				omniHaptics.hapticListener = null; //leave the listener to the GC
+				return null;
+		}else{ 
+			return omniHaptics;
+		}
+	}
+	
+	private OmniHaptics(String threadName){
+		super(threadName);
+		/* get the screen size which will be passed to init methos in order to set up a window
+		 * for the haptic with the same size as the swing one
+		 */
+		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+
+		int screenWidth = (int)screenSize.getWidth();
+		int screenHeight = (int)screenSize.getHeight();
+
+		width =  screenWidth * 5 / 8;
+		height = screenHeight * 5 / 8;
+		
+		newHapticId = false;
+		dumpHapticId = false;
+		shutdown = false;
+		hapticInitFailed = false;
+		nodes = new HashMap<String,ArrayList<Node>>();
+		edges = new HashMap<String,ArrayList<Edge>>();
+		currentNodes = EMPTY_NODE_LIST;
+		currentEdges = EMPTY_EDGE_LIST;
+		nodesMaps = new HashMap<String,HashMap<Integer,Node>>();
+		edgesMaps = new HashMap<String,HashMap<Integer,Edge>>();
+		currentNodesMap = EMPTY_NODE_MAP;
+		currentEdgesMap = EMPTY_EDGE_MAP;
+	}	
+	
+	
+	@Override
+	public native int init(int width, int height) throws IOException;
+	
+	@Override
+	public void addNewDiagram(String name, boolean switchAfter){
+		ArrayList<Node> cNodes = new ArrayList<Node>(30);
+		ArrayList<Edge> cEdges = new ArrayList<Edge>(30);
+		nodes.put(name, cNodes);
+		edges.put(name, cEdges);
+		
+		HashMap<Integer,Node> cNodesMap = new HashMap<Integer,Node>();
+		HashMap<Integer,Edge> cEdgesMap = new HashMap<Integer,Edge>();
+		nodesMaps.put(name, cNodesMap);
+		edgesMaps.put(name, cEdgesMap);
+		
+		if(switchAfter){
+			synchronized(this){
+				currentNodes = cNodes;
+				currentEdges = cEdges;
+				currentNodesMap = cNodesMap;
+				currentEdgesMap = cEdgesMap;
+			}
+		}
+	}
+	
+	@Override
+	public synchronized void switchDiagram(String diagramName){
+		// check nodes only, as the edges and nodes maps are strongly coupled
+		if(!nodes.containsKey(diagramName))
+			throw new IllegalArgumentException("Diagram " + diagramName + " not present among the current ones");
+		
+		currentNodes = nodes.get(diagramName);
+		currentEdges = edges.get(diagramName);
+		currentNodesMap = nodesMaps.get(diagramName);
+		currentEdgesMap = edgesMaps.get(diagramName);
+	}
+	
+	@Override
+	public synchronized void removeDiagram(String diagramNameToRemove, String diagramNameNext){
+		if(!nodes.containsKey(diagramNameToRemove))
+			throw new IllegalArgumentException("Id " + diagramNameToRemove + " not present aong the current ones");
+		
+		nodes.remove(diagramNameToRemove);
+		edges.remove(diagramNameToRemove);
+		nodesMaps.remove(diagramNameToRemove);
+		edgesMaps.remove(diagramNameToRemove);
+		if(diagramNameNext == null){
+			currentNodes = EMPTY_NODE_LIST;
+			currentEdges = EMPTY_EDGE_LIST;
+			currentNodesMap = EMPTY_NODE_MAP;
+			currentEdgesMap = EMPTY_EDGE_MAP;
+		}else{
+			if(!nodes.containsKey(diagramNameNext))
+					throw new IllegalArgumentException("Id " + diagramNameNext + " not present aong the current ones");
+			currentNodes = nodes.get(diagramNameNext);
+			currentEdges = edges.get(diagramNameNext);
+			currentNodesMap = nodesMaps.get(diagramNameNext);
+			currentEdgesMap = edgesMaps.get(diagramNameNext);
+		}
+	}
+	
+	@Override
+	public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName){
+		newHapticId = true;
+		// waits for an identifier from the openGL thread
+		try{
+			wait();
+		}catch(InterruptedException ie){
+			wasInterrupted();
+		}
+		
+		Node n = new Node(x,y,nodeHashCode, currentHapticId);
+		if(diagramName == null){
+			currentNodes.add(n);
+			currentNodesMap.put(currentHapticId, n);
+		}else{
+			nodes.get(diagramName).add(n);
+			nodesMaps.get(diagramName).put(currentHapticId, n);
+		}
+	}
+
+	@Override
+	public synchronized void removeNode(int nodeHashCode, String diagramName){
+		ListIterator<Node> itr = (diagramName == null) ? currentNodes.listIterator() : nodes.get(diagramName).listIterator();
+		boolean found = false;
+		int hID = -1;
+		while(itr.hasNext()){
+			Node n = itr.next();
+			if(n.diagramId == nodeHashCode){
+				hID = n.hapticId;
+				itr.remove();
+				found = true;
+				break;
+			}
+		}
+		assert(found);
+		
+		/* remove the node from the map as well */
+		if(diagramName == null)
+			currentNodesMap.remove(hID);
+		else
+			nodesMaps.get(diagramName).remove(hID);
+		
+		/* set the flag to ask the haptic thread to free the id of the node that's been deleted */
+		dumpHapticId = true;
+		/* share the id to free with the other thread */
+		currentHapticId = hID;
+		try{
+			wait();
+		}catch(InterruptedException ie){
+			wasInterrupted();
+		}
+	}
+
+	@Override
+	public synchronized void moveNode(double x, double y, int nodeHashCode, String diagramName){
+		ArrayList<Node> iterationList = (diagramName == null) ? currentNodes : nodes.get(diagramName); 
+		for(Node n : iterationList){
+			if(n.diagramId == nodeHashCode){
+				n.x = x;
+				n.y = y;
+				break;
+			}
+		}
+	}
+
+	@Override
+	public synchronized void addEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine, String diagramName){
+		// flag the openGL thread the fact we need an identifier
+		newHapticId = true;
+		// waits for an identifier from the openGL thread
+		try{
+			wait();
+		}catch(InterruptedException ie){ 
+			wasInterrupted();
+		}
+		
+		// find the mid point of the line of attraction
+		double pX = Math.min(attractLine.getX1(), attractLine.getX2());
+		double pY = Math.min(attractLine.getY1(), attractLine.getY2());
+		pX += Math.abs(attractLine.getX1() -  attractLine.getX2())/2;
+		pY += Math.abs(attractLine.getY1() -  attractLine.getY2())/2;
+		
+		Edge e = new Edge(edgeHashCode,currentHapticId, xs, ys, adjMatrix, nodeStart, stipplePattern, pX, pY);
+		if(diagramName == null){
+			/* add the edge reference to the Haptic edges list */
+			currentEdges.add(e);
+			/* add the edge reference to the haptic edges map */
+			currentEdgesMap.put(currentHapticId, e);
+		}else{
+			/* add the edge reference to the Haptic edges list */
+			edges.get(diagramName).add(e);
+			/* add the edge reference to the haptic edges map */
+			edgesMaps.get(diagramName).put(currentHapticId, e);
+		}
+	}
+
+	@Override
+	public synchronized void updateEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart,  Line2D attractLine, String diagramName){
+		assert(xs.length == ys.length);
+		
+		for(Edge e : currentEdges){
+			if(e.diagramId == edgeHashCode){
+				e.xs = xs;
+				e.ys = ys;
+				e.size = xs.length;
+				e.adjMatrix = adjMatrix;
+				e.nodeStart = nodeStart;
+				// find the mid point of the line of attraction
+				double pX = Math.min(attractLine.getX1(), attractLine.getX2());
+				double pY = Math.min(attractLine.getY1(), attractLine.getY2());
+				pX += Math.abs(attractLine.getX1() -  attractLine.getX2())/2;
+				pY += Math.abs(attractLine.getY1() -  attractLine.getY2())/2;
+				e.attractPointX = pX;
+				e.attractPointY = pY;
+			}
+		}
+	}
+
+	@Override
+	public synchronized void removeEdge(int edgeHashCode, String diagramName){
+		ListIterator<Edge> itr = (diagramName == null) ? currentEdges.listIterator() : edges.get(diagramName).listIterator(); 
+		boolean found = false;
+		int hID = -1;
+		while(itr.hasNext()){
+			Edge e = itr.next();
+			if(e.diagramId == edgeHashCode){
+				hID = e.hapticId;
+				itr.remove();
+				found = true;
+				break;
+			}
+		}
+		assert(found);
+		
+		/* remove the edge from the map as well */
+		if(diagramName == null)
+			currentEdgesMap.remove(hID);
+		else
+			edgesMaps.get(diagramName).remove(hID);
+		/* set the flag to ask the haptic thread to free the id of the node that's been deleted */
+		dumpHapticId = true;
+		/* share the id to free with the other thread */
+		currentHapticId = hID;
+		try{
+			wait();
+		}catch(InterruptedException ie){
+			wasInterrupted();
+		}
+	}
+
+	@Override
+	public synchronized void attractTo(int elementHashCode){
+		attractToHapticId = findElementHapticID(elementHashCode);
+		attractTo = true;
+	}
+	
+	@Override
+	public synchronized void pickUp(int elementHashCode){
+		pickUpHapticId = findElementHapticID(elementHashCode);
+		pickUp = true;
+	}
+	
+	private int findElementHapticID(int elementHashCode){
+		int hID = -1;
+		boolean found = false;
+		for(Node n : currentNodes){
+			if(n.diagramId == elementHashCode){
+				hID = n.hapticId;
+				found = true;
+				break;
+			}
+		}
+		
+		if(!found)
+			for(Edge e : currentEdges){
+				if(e.diagramId == elementHashCode){
+					hID = e.hapticId;
+					found = true;
+					break;
+				}
+			}
+		assert(found);
+		return hID;
+	}
+	
+	@Override
+	public synchronized void dispose(){
+		shutdown = true;
+		/* wait for the haptic thread to shut down */
+		try {
+			wait();
+		} catch (InterruptedException e) {
+			wasInterrupted();
+		}
+	}
+	
+	@Override
+	public void run() {
+		try {
+			init(width,height);
+		} catch (IOException e) {
+			throw new RuntimeException();// OMNI haptic device doesn't cause any exception
+		}
+	}
+	
+	private void wasInterrupted(){
+		throw new UnsupportedOperationException("Haptics thread interrupted and no catch block implemented");
+	}
+	
+	private Node getNodeFromID(int hID){
+		return currentNodesMap.get(hID);
+	}
+	
+	private Edge getEdgeFromID(int hID){
+		return currentEdgesMap.get(hID);
+	}
+	
+	/* the diagram currently selected */
+	private ArrayList<Node> currentNodes;
+	private ArrayList<Edge> currentEdges;
+	private HashMap<Integer,Node> currentNodesMap;
+	private HashMap<Integer,Edge> currentEdgesMap;
+	
+	/* maps with all the diagrams in the editor */
+	private HashMap<String,ArrayList<Node>> nodes;
+	private HashMap<String,ArrayList<Edge>> edges;
+	private HashMap<String,HashMap<Integer,Node>> nodesMaps;
+	private HashMap<String,HashMap<Integer,Edge>> edgesMaps;
+	private int width;
+	private int height;
+	private int attractToHapticId;
+	private int pickUpHapticId;
+	/* flag for synchronization with the haptic thread*/
+	private boolean newHapticId;
+	private boolean dumpHapticId;
+	private boolean shutdown;
+	boolean hapticInitFailed;
+	private boolean attractTo;
+	private boolean pickUp;
+	/* currentHapticId is used to share haptic ids between the threads */
+	private int currentHapticId;
+	private HapticListener hapticListener;
+	
+	private static final ArrayList<Node> EMPTY_NODE_LIST = new ArrayList<Node>(0);
+	private static final ArrayList<Edge> EMPTY_EDGE_LIST = new ArrayList<Edge>(0);
+	private static final HashMap<Integer,Node> EMPTY_NODE_MAP = new HashMap<Integer,Node>();
+	private static final HashMap<Integer,Edge> EMPTY_EDGE_MAP = new HashMap<Integer,Edge>();
+
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java	Wed Apr 25 17:09:09 2012 +0100
@@ -23,12 +23,14 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
+import java.text.MessageFormat;
 import java.util.ResourceBundle;
 
 import javax.swing.SwingUtilities;
 
 import uk.ac.qmul.eecs.ccmi.gui.EditorFrame;
 import uk.ac.qmul.eecs.ccmi.gui.HapticKindle;
+import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
 import uk.ac.qmul.eecs.ccmi.gui.TemplateEditor;
 import uk.ac.qmul.eecs.ccmi.haptics.Haptics;
 import uk.ac.qmul.eecs.ccmi.haptics.HapticsFactory;
@@ -38,6 +40,7 @@
 import uk.ac.qmul.eecs.ccmi.utils.CCmIUncaughtExceptionHandler;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter;
 
 /**
  * 
@@ -49,8 +52,9 @@
 	
 	/**
 	 *  perform initialization prior to displaying the GUI 
+	 * @throws IOException 
 	 */
-	public void init(String[] args) {
+	public void init(String[] args) throws IOException {
 		Thread.setDefaultUncaughtExceptionHandler(new CCmIUncaughtExceptionHandler());
 		final ResourceBundle resources = ResourceBundle.getBundle(this.getClass().getName());
 		/* read command line arguments */
@@ -77,47 +81,51 @@
 			preferences.put("home", homeDirPath);
 		}
 		File homeDir = new File(homeDirPath);
-		homeDir.mkdir();
+		mkDir(homeDir, resources);
 		
 		File backupDir = new File(homeDir,resources.getString("dir.backups"));
-		backupDir.mkdir();
+		mkDir(backupDir, resources);
 		backupDirPath = backupDir.getAbsolutePath();
 		
 		/* create the templates directory into the home directory */ 
 		File templateDir = new File(homeDir,resources.getString("dir.templates"));
-		templateDir.mkdir();
+		mkDir(templateDir,resources);
 		
 		/* create the images directory into the home directory */
 		File imagesDir = new File(homeDir,resources.getString("dir.images"));
-		if(imagesDir.mkdir())
+		if(mkDir(imagesDir,resources))
 			preferences.put("dir.images", imagesDir.getAbsolutePath());
 		
 		/* create the diagrams dir into the home directory */
 		File diagramDir = new File(homeDir,resources.getString("dir.diagrams"));
-		if(diagramDir.mkdir())
+		if(mkDir(diagramDir,resources))
 			preferences.put("dir.diagrams", diagramDir.getAbsolutePath());
 		
 		/* create the libs directory into he home directory */
 		File libsDir = new File(homeDir,resources.getString("dir.libs"));
-		if(libsDir.mkdir())
+		if(mkDir(libsDir,resources))
 			preferences.put("dir.libs", libsDir.getAbsolutePath());
 		
+		/* write the template files included in the software in the template dir, if they don't exist yet */
+		ResourceFileWriter resourceWriter = new ResourceFileWriter(getClass().getResource("UML Diagram.xml"));
+		resourceWriter.writeToDisk(templateDir.getAbsolutePath(),"UML Diagram.xml");
+		resourceWriter.serResource(getClass().getResource("Tube.xml"));
+		resourceWriter.writeToDisk(templateDir.getAbsolutePath(),"Tube.xml");
+		resourceWriter.serResource(getClass().getResource("Organization Chart.xml"));
+		resourceWriter.writeToDisk(templateDir.getAbsolutePath(),"Organization Chart.xml");
+		
 		/* read the template files into an array to pass to the EditorFrame instance */
-		if(templateDir.exists()){
-			FilenameFilter filter = new FilenameFilter() {
-				@Override
-			    public boolean accept(File f, String name) {
-			    	return (name.endsWith(resources.getString("template.extension")));
-			    }
-			};
-			templateFiles = templateDir.listFiles(filter);
-		}else{
-			templateFiles = new File[0];
-		}
+		FilenameFilter filter = new FilenameFilter() {
+			@Override
+		    public boolean accept(File f, String name) {
+		    	return (name.endsWith(resources.getString("template.extension")));
+		    }
+		};
+		templateFiles = templateDir.listFiles(filter);
 
 		if(enableLog){
 			File logDir = new File(homeDir,resources.getString("dir.log"));
-			logDir.mkdir();
+			mkDir(logDir,resources);
 			try{
 				InteractionLog.enable(logDir.getAbsolutePath());
 				InteractionLog.log("PROGRAM STARTED");
@@ -128,15 +136,24 @@
 			}
 		}
 		
+		/* create sound, speech and haptic engines */
 		NarratorFactory.createInstance();
 		SoundFactory.createInstance();
 		
-		hapticKindle = new HapticKindle();
-		HapticsFactory.createInstance(hapticKindle);
+		HapticsFactory.createInstance(new HapticKindle());
 		haptics = HapticsFactory.getInstance();
-		if(!haptics.isAlive()){
-			hapticKindle = null;
-		}
+		if(haptics.isAlive())
+			NarratorFactory.getInstance().speakWholeText("Haptic device successfully initialized");
+	}
+	
+	private boolean mkDir(File dir,ResourceBundle resources) throws IOException{
+		boolean created = dir.mkdir();
+		if(!dir.exists())
+			throw new IOException(MessageFormat.format(
+					resources.getString("dir.error_msg"),
+					dir.getAbsolutePath())
+					);
+		return created;
 	}
 	
 	/**
@@ -145,8 +162,8 @@
 	@Override
 	public void run() {
 		editorFrame = new EditorFrame(haptics,templateFiles,backupDirPath,getTemplateEditors());
-		if(hapticKindle != null)
-			hapticKindle.setEditorFrame(editorFrame);
+//		if(hapticKindle != null)
+//			hapticKindle.setEditorFrame(editorFrame);
 	}
 	
 	public TemplateEditor[] getTemplateEditors(){
@@ -161,7 +178,24 @@
 	public static void main(String[] args) {
 		DiagramEditorApp application = new DiagramEditorApp();
 		
-		application.init(args);
+		try {
+			application.init(args);
+		} catch (IOException e) {
+			final String msg = e.getLocalizedMessage();
+			try {
+				SwingUtilities.invokeAndWait(new Runnable(){
+					@Override
+					public void run(){
+						SpeechOptionPane.showMessageDialog(null, msg);
+					}
+				});
+				System.exit(-1);
+			} catch (InterruptedException ex) {
+				throw new RuntimeException(ex);
+			} catch (InvocationTargetException ex) {
+				throw new RuntimeException(ex);
+			}
+		}
 		
 		try {
             SwingUtilities.invokeAndWait(application);
@@ -172,8 +206,11 @@
         }    
 	}
 	
-	EditorFrame editorFrame;
-	HapticKindle hapticKindle;
+	public static EditorFrame getFrame(){
+		return editorFrame;
+	}
+	
+	static EditorFrame editorFrame;
 	Haptics haptics;
 	File[] templateFiles;
 	TemplateEditor[] templateCreators;
--- a/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.properties	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.properties	Wed Apr 25 17:09:09 2012 +0100
@@ -9,6 +9,9 @@
 dir.log=log
 usage=Unrecognized option(s)\nUsage : java -jar ccmi.jar [-l] \n  -l : enables interaction log
 
+dir.error_msg=Could not create the following directory: {0}\u000A\
+Please check directory permissions and try again.
+
 
 #### APPLICATION PREFERENCES ####
 # server.local_port
@@ -20,3 +23,8 @@
 # laf 
 # recent
 # use_accessible_filechooser
+# user.id
+# server.address
+# server.local_port
+# server.remote_port
+# second_voice_enabled
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/main/Organization Chart.xml	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<Diagram Name="Organization Chart" PrototypeDelegate="uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleShapePrototypePersistenceDelegate">
+  <Prototypes>
+    <Node>
+      <Type>Entity</Type>
+      <ShapeType>Rectangle</ShapeType>
+      <Properties>
+        <Property>
+          <Type>Responsibility</Type>
+          <View Position="Inside" ShapeType="Rectangle"/>
+        </Property>
+      </Properties>
+    </Node>
+    <Edge>
+      <Type>Connection</Type>
+      <LineStyle>Solid</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+    </Edge>
+  </Prototypes>
+</Diagram>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/main/Tube.xml	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<Diagram Name="Tube" PrototypeDelegate="uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleShapePrototypePersistenceDelegate">
+  <Prototypes>
+    <Node>
+      <Type>Stations</Type>
+      <ShapeType>Circle</ShapeType>
+      <Properties/>
+    </Node>
+    <Edge>
+      <Type>Central Line</Type>
+      <LineStyle>Solid</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+    </Edge>
+    <Edge>
+      <Type>Victoria Line</Type>
+      <LineStyle>Solid</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+    </Edge>
+    <Edge>
+      <Type>Jubilee Line</Type>
+      <LineStyle>Solid</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+    </Edge>
+    <Edge>
+      <Type>Piccadilly Line</Type>
+      <LineStyle>Solid</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+    </Edge>
+    <Edge>
+      <Type>Circle Line</Type>
+      <LineStyle>Solid</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+    </Edge>
+  </Prototypes>
+</Diagram>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/main/UML Diagram.xml	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<Diagram Name="UML Diagram" PrototypeDelegate="uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleShapePrototypePersistenceDelegate">
+  <Prototypes>
+    <Node>
+      <Type>Class</Type>
+      <ShapeType>Rectangle</ShapeType>
+      <Properties>
+        <Property>
+          <Type>attributes</Type>
+          <View Position="Inside" ShapeType="Transparent"/>
+          <Modifiers>
+            <Modifier id="0">
+              <Type>static</Type>
+              <View Bold="false" Italic="true" Prefix="" Suffix="" Underline="false"/>
+            </Modifier>
+            <Modifier id="1">
+              <Type>protected</Type>
+              <View Bold="false" Italic="false" Prefix="#" Suffix="" Underline="false"/>
+            </Modifier>
+            <Modifier id="2">
+              <Type>private</Type>
+              <View Bold="false" Italic="false" Prefix="-" Suffix="" Underline="false"/>
+            </Modifier>
+          </Modifiers>
+        </Property>
+        <Property>
+          <Type>operations</Type>
+          <View Position="Inside" ShapeType="Transparent"/>
+          <Modifiers>
+            <Modifier id="0">
+              <Type>static</Type>
+              <View Bold="false" Italic="true" Prefix="" Suffix="" Underline="false"/>
+            </Modifier>
+            <Modifier id="1">
+              <Type>protected</Type>
+              <View Bold="false" Italic="false" Prefix="#" Suffix="" Underline="false"/>
+            </Modifier>
+            <Modifier id="2">
+              <Type>private</Type>
+              <View Bold="false" Italic="false" Prefix="-" Suffix="" Underline="false"/>
+            </Modifier>
+          </Modifiers>
+        </Property>
+      </Properties>
+    </Node>
+    <Edge>
+      <Type>Association</Type>
+      <LineStyle>Solid</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+      <Heads>
+        <Head Head="Tail" headLabel="from"/>
+        <Head Head="V" headLabel="to"/>
+      </Heads>
+    </Edge>
+    <Edge>
+      <Type>Generalization</Type>
+      <LineStyle>Dashed</LineStyle>
+      <MinAttachedNodes>2</MinAttachedNodes>
+      <MaxAttachedNodes>2</MaxAttachedNodes>
+      <Heads>
+        <Head Head="Tail" headLabel="sub class"/>
+        <Head Head="Triangle" headLabel="super class"/>
+      </Heads>
+    </Edge>
+  </Prototypes>
+</Diagram>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/AwarenessMessage.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,80 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.network;
+
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+public class AwarenessMessage extends Message {
+
+	public AwarenessMessage(long timestamp, Name name, String diagram, DiagramEventActionSource source) {
+		super(timestamp, diagram, source);
+		this.name = name;
+	}
+
+	public AwarenessMessage(Name name, String diagram, DiagramEventActionSource source) {
+		super(diagram, source);
+		this.name = name;
+	}
+	
+	public AwarenessMessage(Name name, String diagram, String source) {
+		super(diagram, source);
+		this.name = name;
+	}
+
+	@Override
+	public Name getName() {
+		return name;
+	}
+	
+	public static Name valueOf(String n){
+		Name name = Name.NONE_A;
+		try {
+			name = Name.valueOf(n);
+		}catch(IllegalArgumentException iae){
+			iae.printStackTrace();
+		}
+		return name;
+	}
+	
+	public final static String NAME_POSTFIX = "_A";
+	public static final String USERNAMES_SEPARATOR = "\n";
+	private Name name;
+	private static String defaultUserName = PreferencesService.getInstance().get("user.name", System.getProperty("user.name"));
+
+	public enum Name implements Message.MessageName {
+		START_A,
+		STOP_A,
+		GOON_A,
+		USERNAME_A,
+		ERROR_A,
+		NONE_A;
+	};
+	
+	public static String getDefaultUserName(){
+		synchronized(AwarenessMessage.class){
+			return defaultUserName;
+		}
+	}
+	
+	public static void setDefaultUserName(String userName){
+		synchronized(AwarenessMessage.class){
+			defaultUserName = userName ;
+		}
+	}
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/CCmIOSCPacketCodec.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/CCmIOSCPacketCodec.java	Wed Apr 25 17:09:09 2012 +0100
@@ -51,6 +51,5 @@
 		super.encodeBundle(bndl, b);
 		b.position("#bundle\0".length());
 		long timestamp = b.getLong();
-		System.out.println("time in encode b" + timestamp);
 	}*/
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ClientConnectionManager.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ClientConnectionManager.java	Wed Apr 25 17:09:09 2012 +0100
@@ -26,12 +26,10 @@
 import java.nio.channels.Selector;
 import java.nio.channels.SocketChannel;
 import java.text.MessageFormat;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 import java.util.ResourceBundle;
 import java.util.Set;
@@ -41,18 +39,19 @@
 
 import javax.swing.SwingUtilities;
 
-import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.gui.DiagramPanel;
 import uk.ac.qmul.eecs.ccmi.gui.Edge;
 import uk.ac.qmul.eecs.ccmi.gui.EditorTabbedPane;
 import uk.ac.qmul.eecs.ccmi.gui.Finder;
 import uk.ac.qmul.eecs.ccmi.gui.Node;
 import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter;
+import uk.ac.qmul.eecs.ccmi.speech.Narrator;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 
 /**
@@ -63,10 +62,10 @@
  * issued by the server, the element is created from scratch, according to the message of the server.     
  * 
  */
-public class ClientConnectionManager extends Thread {
+public class ClientConnectionManager extends NetworkThread {
 	 
 	public ClientConnectionManager(EditorTabbedPane tabbedPane) throws IOException{
-		super("Client Connection Manager");
+		super("Network Client Thread");
 		channels = new HashMap<SocketChannel, Diagram>();
 		requests = new ConcurrentLinkedQueue<Request>();
 		answers = new LinkedBlockingQueue<Answer>();
@@ -75,7 +74,7 @@
 		this.tabbedPane = tabbedPane;
 		protocol = ProtocolFactory.newInstance();
 		mustSayGoodbye = false;
-		mustAnswer = false;
+		waitingAnswer = false;
 	}
 	
 	/**
@@ -85,12 +84,19 @@
 	 */
 	public void addRequest(Request r){
 			requests.add(r);
+			if(r instanceof SendLockRequest){
+				SendLockRequest slr = (SendLockRequest) r;
+				if(slr.lock.getName().toString().startsWith(LockMessage.GET_LOCK_PREFIX))
+					waitingAnswer = true; 
+			}
 			selector.wakeup();
 	}
 	
 	public Answer getAnswer(){
 		try {
-			return answers.take();
+			Answer answer = answers.take();
+			waitingAnswer = false;
+			return answer;
 		} catch (InterruptedException e) {
 			throw new RuntimeException(e);// must never happen
 		}
@@ -108,7 +114,7 @@
 			if(mustSayGoodbye)
 				break;
 		
-			/* handle the requests for the server from the local users */
+			/* handle the requests for the remote server from the local users */
 			handleRequests();
 			
 			for (Iterator<SelectionKey> itr = selector.selectedKeys().iterator(); itr.hasNext();){
@@ -124,111 +130,106 @@
 					try {
 						msg = protocol.receiveMessage(channel);
 					} catch (IOException e) {
-						revertDiagram(channel);
+						revertDiagram(channel,false);
 						/* signal the event dispatching thread, otherwise blocked */
-						if(mustAnswer){
-							try {
-								answers.put(new ErrorAnswer());
-							} catch (InterruptedException ie) {
-								throw new RuntimeException(ie);
-							}
+						try {
+							/* RevertedDiagramAnswer is to prevent the Event dispatching Thread from blocking if the   *
+							 * server goes down and the client is still waiting for an answer. If the thread *
+							 *  was not waiting for an answers the RevertedDiagramAnswer will not be put in the queue  */
+							if(waitingAnswer)
+								answers.put(new RevertedDiagramAnswer());
+						} catch (InterruptedException ie) {
+							throw new RuntimeException(ie);
 						}
 						continue;
 					}
 					//System.out.println("ClientConnaectionManager: read message " + msg.getName());
 					/* retrieve the diagram */
-					@SuppressWarnings("unused")
 					String diagramName = msg.getDiagram();
 					final Diagram diagram = channels.get(channel);
 					node = null;
 					edge = null;
 					if(msg instanceof Command){
 						final Command cmd = (Command)msg; 	
+						cmd.getSource().setDiagramName(diagram.getName());
 						/* log the command through the interaction log, if any */
 						Command.log(cmd, "remote command received");
 						switch(cmd.getName()){
 							case INSERT_NODE :
 								double dx = (Double)cmd.getArgAt(1);
 								double dy = (Double)cmd.getArgAt(2);
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((String)cmd.getArgAt(0), diagram.getNodePrototypes());
-									node = (Node)node.clone();
-									/* Place the top left corner of the bounds at the origin. It might be different from     *
-				            		 * the origin, as it depends on how the clonation is implemented internally. These calls * 
-				            		 * to translate are not notified to any listener as the node is not n the model yet      */
-				            		Rectangle2D bounds = node.getBounds();
-				            		node.translate(new Point2D.Double(),-bounds.getX(),-bounds.getY());
-									/* perform the actual translation from the origin */
-				            		node.translate(new Point2D.Double(), dx, dy);
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), node));
+								node = Finder.findNode((String)cmd.getArgAt(0), diagram.getNodePrototypes());
+								node = (Node)node.clone();
+								/* Place the top left corner of the bounds at the origin. It might be different from     *
+			            		 * the origin, as it depends on how the clonation is implemented internally. These calls * 
+			            		 * to translate are not notified to any listener as the node is not n the model yet      */
+			            		Rectangle2D bounds = node.getBounds();
+			            		node.translate(new Point2D.Double(),-bounds.getX(),-bounds.getY(), DiagramEventSource.NONE);
+								/* perform the actual translation from the origin */
+			            		node.translate(new Point2D.Double(),dx,dy,DiagramEventSource.NONE);
+								SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), node, null, cmd.getSource()));
 								break;
 							case INSERT_EDGE :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((String)cmd.getArgAt(0), diagram.getEdgePrototypes());
-									edge = (Edge)edge.clone();
-									List<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(cmd.getArgNum()-1);
-									for(int i=1;i<cmd.getArgNum();i++)
-										nodesToConnect.add(Finder.findNode((Long)cmd.getArgAt(i), diagram.getCollectionModel().getNodes()));
-									try {
-										edge.connect(nodesToConnect);
-									} catch (ConnectNodesException e) {
-										throw new RuntimeException();//this must never happen as the check is done by the client before issuing the command
-									}
-									SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), edge));
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((String)cmd.getArgAt(0), diagram.getEdgePrototypes());
+								edge = (Edge)edge.clone();
+								long[] nodesToConnect = new long[cmd.getArgNum()-1];
+								for(int i=0;i<nodesToConnect.length;i++)
+									nodesToConnect[i] = (Long)cmd.getArgAt(i+1);
+								SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), edge, nodesToConnect, cmd.getSource()));
+								diagram.getCollectionModel().getMonitor().unlock();
 								break;
 							case REMOVE_NODE :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.Remove(diagram.getCollectionModel(),node));
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
+								SwingUtilities.invokeLater(new CommandExecutor.Remove(diagram.getCollectionModel(),node,cmd.getSource()));
 								break;
 							case REMOVE_EDGE :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((Long)cmd.getArgAt(0), diagram.getCollectionModel().getEdges());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.Remove(diagram.getCollectionModel(),edge));
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((Long)cmd.getArgAt(0), diagram.getCollectionModel().getEdges());
+								diagram.getCollectionModel().getMonitor().unlock();
+								SwingUtilities.invokeLater(new CommandExecutor.Remove(diagram.getCollectionModel(),edge,cmd.getSource()));
 								break;
 							case SET_EDGE_NAME :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((Long)cmd.getArgAt(0), diagram.getCollectionModel().getEdges());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.SetName(edge, (String)cmd.getArgAt(1)));
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((Long)cmd.getArgAt(0), diagram.getCollectionModel().getEdges());
+								diagram.getCollectionModel().getMonitor().unlock();
+								SwingUtilities.invokeLater(new CommandExecutor.SetName(edge, (String)cmd.getArgAt(1),cmd.getSource()));
 								break;
 							case SET_NODE_NAME :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.SetName(node, (String)cmd.getArgAt(1)));
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
+								SwingUtilities.invokeLater(new CommandExecutor.SetName(node, (String)cmd.getArgAt(1),cmd.getSource()));
 								break;
 							case SET_PROPERTY : 
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.SetProperty(
 										node,
 										(String)cmd.getArgAt(1),
 										(Integer)cmd.getArgAt(2),
-										(String)cmd.getArgAt(3)
+										(String)cmd.getArgAt(3),
+										cmd.getSource()
 										));
 								break;
 							case SET_PROPERTIES :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-								}
-								NodeProperties properties = node.getPropertiesCopy();
-								properties.valueOf((String)cmd.getArgAt(1));
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.SetProperties(
 										node,
-										properties
+										(String)cmd.getArgAt(1),
+										cmd.getSource()
 										));
 								break;		
 							case CLEAR_PROPERTIES :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.ClearProperties(node));
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
+								SwingUtilities.invokeLater(new CommandExecutor.ClearProperties(node,cmd.getSource()));
 								break;	
 							case SET_NOTES :
 								int[] path = new int[cmd.getArgNum()-1];
@@ -236,89 +237,92 @@
 									path[i] = (Integer)cmd.getArgAt(i);
 								}
 								final String notes = (String)cmd.getArgAt(cmd.getArgNum()-1);
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									treeNode = Finder.findTreeNode(path, (DiagramModelTreeNode)diagram.getTreeModel().getRoot());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.SetNotes(diagram.getTreeModel(),treeNode, notes));
+								diagram.getCollectionModel().getMonitor().lock();
+								treeNode = Finder.findTreeNode(path, (DiagramTreeNode)diagram.getTreeModel().getRoot());
+								diagram.getCollectionModel().getMonitor().unlock();;
+								SwingUtilities.invokeLater(new CommandExecutor.SetNotes(diagram.getTreeModel(),treeNode, notes,cmd.getSource()));
 								break;
 							case ADD_PROPERTY :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.AddProperty(
 										node, 
 										(String)cmd.getArgAt(1), 
-										(String)cmd.getArgAt(2)
+										(String)cmd.getArgAt(2),
+										cmd.getSource()
 										));
 								break;
 							case REMOVE_PROPERTY :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.RemoveProperty(
 										node, 
 										(String)cmd.getArgAt(1),
-										(Integer)cmd.getArgAt(2)));
+										(Integer)cmd.getArgAt(2),
+										cmd.getSource()));
 								break;
 							case SET_MODIFIERS :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-									indexes = new HashSet<Integer>(cmd.getArgNum()-3);
-									for(int i=3;i<cmd.getArgNum();i++){
-										indexes.add((Integer)cmd.getArgAt(i));
-									}
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								indexes = new HashSet<Integer>(cmd.getArgNum()-3);
+								for(int i=3;i<cmd.getArgNum();i++){
+									indexes.add((Integer)cmd.getArgAt(i));
 								}
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.SetModifiers(
 										node,
 										(String)cmd.getArgAt(1),
 										(Integer)cmd.getArgAt(2),
-										indexes
-										));
+										indexes,
+										cmd.getSource()));
 								break;
 							case SET_ENDLABEL :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-									node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.SetEndLabel(edge, node,(String)cmd.getArgAt(2)));
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+								node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
+								SwingUtilities.invokeLater(new CommandExecutor.SetEndLabel(edge, node,(String)cmd.getArgAt(2),cmd.getSource()));
 								break;
 							case SET_ENDDESCRIPTION :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-									node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+								node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.SetEndDescription(
 										edge, 
 										node, 
-										(Integer)cmd.getArgAt(2)
+										(Integer)cmd.getArgAt(2),
+										cmd.getSource()
 										));
 								break;		
 							case TRANSLATE_NODE :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.Translate(
 										node,
 										new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
 										(Double)cmd.getArgAt(3), 
-										(Double)cmd.getArgAt(4)
-										));
+										(Double)cmd.getArgAt(4),
+										cmd.getSource()));
 								break;
 							case TRANSLATE_EDGE :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+								diagram.getCollectionModel().getMonitor().unlock();
 								SwingUtilities.invokeLater(new CommandExecutor.Translate(
 										edge,
 										new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
 										(Double)cmd.getArgAt(3), 
-										(Double)cmd.getArgAt(4)
-									));
+										(Double)cmd.getArgAt(4),
+										cmd.getSource()));
 								break;
 							case BEND :
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-								}
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+								diagram.getCollectionModel().getMonitor().unlock();
 								Point2D bendStart = null;
 								if(cmd.getArgNum() == 5){
 									bendStart = new Point2D.Double((Double)cmd.getArgAt(3),(Double)cmd.getArgAt(4));
@@ -326,18 +330,18 @@
 								SwingUtilities.invokeLater(new CommandExecutor.Bend(
 										edge,
 										new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
-										bendStart
-									));
+										bendStart,
+										cmd.getSource()));
 								break;
 							case STOP_EDGE_MOVE : 
-								synchronized(diagram.getCollectionModel().getMonitor()){
-									edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-								}
-								SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge));
+								diagram.getCollectionModel().getMonitor().lock();
+								edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+								diagram.getCollectionModel().getMonitor().unlock();
+								SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge,cmd.getSource()));
 								break;
 							case STOP_NODE_MOVE :
 								node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
-								SwingUtilities.invokeLater(new CommandExecutor.StopMove(node));
+								SwingUtilities.invokeLater(new CommandExecutor.StopMove(node,cmd.getSource()));
 								break;
 							}
 					}else if(msg instanceof Reply){
@@ -356,56 +360,55 @@
 						switch(reply.getName()){
 							case INSERT_NODE_R :
 							case INSERT_EDGE_R :
-								SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), sendCmdRequest.element));
+								SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), sendCmdRequest.element, null, reply.getSource()));
 								break;
 							case REMOVE_EDGE_R :
 							case REMOVE_NODE_R :
-								SwingUtilities.invokeLater(new CommandExecutor.Remove(diagram.getCollectionModel(), sendCmdRequest.element));
+								SwingUtilities.invokeLater(new CommandExecutor.Remove(diagram.getCollectionModel(), sendCmdRequest.element,reply.getSource()));
 								break;
 							case SET_EDGE_NAME_R :
 							case SET_NODE_NAME_R :	
-								SwingUtilities.invokeLater(new CommandExecutor.SetName(sendCmdRequest.element, (String)sendCmdRequest.cmd.getArgAt(1)));
+								SwingUtilities.invokeLater(new CommandExecutor.SetName(sendCmdRequest.element, (String)sendCmdRequest.cmd.getArgAt(1),reply.getSource()));
 								break;
 							case SET_PROPERTY_R :
 								SwingUtilities.invokeLater(new CommandExecutor.SetProperty(
 									(Node)sendCmdRequest.element,
 									(String)sendCmdRequest.cmd.getArgAt(1),
 									(Integer)sendCmdRequest.cmd.getArgAt(2),
-									(String)sendCmdRequest.cmd.getArgAt(3)
+									(String)sendCmdRequest.cmd.getArgAt(3),
+									reply.getSource()
 								));
 								break;
 							case SET_PROPERTIES_R :
 								Node n = (Node)sendCmdRequest.element;
-								NodeProperties properties = n.getPropertiesCopy();
-								properties.valueOf((String)sendCmdRequest.cmd.getArgAt(1));
 								SwingUtilities.invokeLater(new CommandExecutor.SetProperties(
 										n,
-										properties 
-								));
+										(String)sendCmdRequest.cmd.getArgAt(1),
+										reply.getSource()));
 								break;
 							case CLEAR_PROPERTIES_R :
-								SwingUtilities.invokeLater(new CommandExecutor.ClearProperties((Node)sendCmdRequest.element));
+								SwingUtilities.invokeLater(new CommandExecutor.ClearProperties((Node)sendCmdRequest.element,reply.getSource()));
 								break;	
 							case SET_NOTES_R :
 								SwingUtilities.invokeLater(new CommandExecutor.SetNotes(
 										diagram.getTreeModel(),
 										((SendTreeCmdRequest)sendCmdRequest).treeNode,
-										(String)sendCmdRequest.cmd.getArgAt(sendCmdRequest.cmd.getArgNum()-1)
-										));
+										(String)sendCmdRequest.cmd.getArgAt(sendCmdRequest.cmd.getArgNum()-1),
+										reply.getSource()));
 								break;
 							case ADD_PROPERTY_R :
 								SwingUtilities.invokeLater(new CommandExecutor.AddProperty(
 									(Node)sendCmdRequest.element,
 									(String)sendCmdRequest.cmd.getArgAt(1),
-									(String)sendCmdRequest.cmd.getArgAt(2)
-									));
+									(String)sendCmdRequest.cmd.getArgAt(2),
+									reply.getSource()));
 								break;
 							case REMOVE_PROPERTY_R :
 								SwingUtilities.invokeLater(new CommandExecutor.RemoveProperty(
 										(Node)sendCmdRequest.element,
 										(String)sendCmdRequest.cmd.getArgAt(1),
-										(Integer)sendCmdRequest.cmd.getArgAt(2)
-										));
+										(Integer)sendCmdRequest.cmd.getArgAt(2),
+										reply.getSource()));
 								break;
 							case SET_MODIFIERS_R :
 								indexes = new HashSet<Integer>(sendCmdRequest.cmd.getArgNum()-3);
@@ -416,8 +419,8 @@
 										(Node)sendCmdRequest.element,
 										(String)sendCmdRequest.cmd.getArgAt(1), 
 										(Integer)sendCmdRequest.cmd.getArgAt(2), 
-										indexes
-									));
+										indexes,
+										reply.getSource()));
 								break;
 							case SET_ENDLABEL_R :
 								synchronized(diagram.getCollectionModel().getMonitor()){
@@ -426,8 +429,8 @@
 								SwingUtilities.invokeLater(new CommandExecutor.SetEndLabel(
 										(Edge)sendCmdRequest.element,
 										node, 
-										(String)sendCmdRequest.cmd.getArgAt(2)
-										));
+										(String)sendCmdRequest.cmd.getArgAt(2),
+										reply.getSource()));
 								break;
 							case SET_ENDDESCRIPTION_R :
 								synchronized(diagram.getCollectionModel().getMonitor()){
@@ -437,8 +440,8 @@
 										(Edge)sendCmdRequest.element,
 										node, 
 										/* if the endDescription ain't included then we have to set it to null ( = NONE )*/
-										sendCmdRequest.cmd.getArgNum() == 3 ? (Integer)sendCmdRequest.cmd.getArgAt(2): -1
-									));
+										sendCmdRequest.cmd.getArgNum() == 3 ? (Integer)sendCmdRequest.cmd.getArgAt(2): -1,
+										reply.getSource()));
 								break;	
 							case TRANSLATE_NODE_R :
 								node = (Node)sendCmdRequest.element;
@@ -446,8 +449,8 @@
 										node,
 										new Point2D.Double((Double)sendCmdRequest.cmd.getArgAt(1),(Double)sendCmdRequest.cmd.getArgAt(2)), 
 										(Double)sendCmdRequest.cmd.getArgAt(3), 
-										(Double)sendCmdRequest.cmd.getArgAt(4)
-										));
+										(Double)sendCmdRequest.cmd.getArgAt(4),
+										reply.getSource()));
 								break;
 							case TRANSLATE_EDGE_R :
 								edge = (Edge)sendCmdRequest.element;
@@ -455,7 +458,8 @@
 										edge,
 										new Point2D.Double((Double)sendCmdRequest.cmd.getArgAt(1),(Double)sendCmdRequest.cmd.getArgAt(2)), 
 										(Double)sendCmdRequest.cmd.getArgAt(3), 
-										(Double)sendCmdRequest.cmd.getArgAt(4)
+										(Double)sendCmdRequest.cmd.getArgAt(4),
+										reply.getSource()
 										));
 								break;	
 							case BEND_R :
@@ -467,30 +471,72 @@
 								SwingUtilities.invokeLater(new CommandExecutor.Bend(
 										edge,
 										new Point2D.Double((Double)sendCmdRequest.cmd.getArgAt(1),(Double)sendCmdRequest.cmd.getArgAt(2)),
-										bendStart
+										bendStart,
+										reply.getSource()
 									));
 								break;
 							case STOP_EDGE_MOVE_R : 
 								edge = (Edge)sendCmdRequest.element;
-								SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge));
+								SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge,reply.getSource()));
 								break;
 							case STOP_NODE_MOVE_R :
 								node = (Node)sendCmdRequest.element;
-								SwingUtilities.invokeLater(new CommandExecutor.StopMove(node));
+								SwingUtilities.invokeLater(new CommandExecutor.StopMove(node,reply.getSource()));
 								break;
 							case ERROR_R :
-								SwingUtilities.invokeLater(new CommandExecutor.ShowErrorMessageDialog(tabbedPane, "Error for command on "+ sendCmdRequest.element.getName()+ ". " +reply.getMessage()));
+								SwingUtilities.invokeLater(new CommandExecutor.ShowErrorMessageDialog(tabbedPane, "Error for command on "+ sendCmdRequest.element.getName()+ ". " +reply.getMessage(),reply.getSource()));
 								InteractionLog.log("SERVER", "error:reply from server", DiagramElement.toLogString(sendCmdRequest.element) + " " +reply.getMessage());
 								break;
 							default : throw new RuntimeException("Reply message not recognized: "+reply.getName());
 						}
-					}else{ // lock message from the server 
+					}else if(msg instanceof LockMessage){ // lock message from the server 
 						try {
 							answers.put(new LockAnswer((LockMessage)msg));
-							mustAnswer = false;
 						} catch (InterruptedException e) {
 							throw new RuntimeException(e); // must never happen
 						}
+					}else{ // awareness message
+						AwarenessMessage awMsg = (AwarenessMessage)msg;
+						DisplayFilter filter = DisplayFilter.getInstance();
+						if(filter != null){// if awareness panel ain't open, drop the packet
+							switch(awMsg.getName()){
+							case START_A :{
+								if(filter.configurationHasChanged()){
+									for(Diagram d : channels.values())
+										getAwarenessPanelEditor().clearRecords(d.getName());
+								}
+								DiagramEventActionSource actionSource = (DiagramEventActionSource)awMsg.getSource();
+								if(actionSource.getCmd() == Command.Name.INSERT_NODE || actionSource.getCmd() == Command.Name.INSERT_EDGE || actionSource.getCmd() == Command.Name.SELECT_NODE_FOR_EDGE_CREATION)
+									getAwarenessPanelEditor().addTimedRecord(diagramName, filter.processForText(actionSource));
+								else
+									getAwarenessPanelEditor().addRecord(diagramName, filter.processForText(actionSource));
+								/* announce the just received awareness message via the second voice */
+								NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(actionSource), Narrator.SECOND_VOICE);
+							}break;
+							case STOP_A : {
+								if(filter.configurationHasChanged()){
+									for(Diagram d : channels.values())
+										getAwarenessPanelEditor().clearRecords(d.getName());
+								}
+								DiagramEventActionSource actionSource = (DiagramEventActionSource)awMsg.getSource();
+								/* unselect node for edge creation is announced and put temporarily on the text panel        *
+								 * (select node for edge creation is temporary as well so we don't need to clean the panel)  */
+								if(actionSource.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION){
+									getAwarenessPanelEditor().addTimedRecord(diagramName, filter.processForText(actionSource));
+									NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(actionSource));
+								}else{
+									getAwarenessPanelEditor().removeRecord(diagramName,filter.processForText(actionSource));
+								}
+							}break;
+							case USERNAME_A : {
+								String userNames = (String)awMsg.getSource();
+								getAwarenessPanelEditor().replaceUserName(diagramName,userNames);
+							}break;
+							case ERROR_A : {
+								SpeechOptionPane.showMessageDialog(tabbedPane, (String)awMsg.getSource());
+							}break;
+							}
+						}
 					}
 				}
 			}
@@ -519,17 +565,19 @@
 					/* something went wrong, turn the diagram back into a local one */
 					/* put the entry in channels just for a moment as it will be used in revertDiagram */
 					channels.put(adr.channel, adr.diagram); 
-					revertDiagram(adr.channel);
+					revertDiagram(adr.channel,false);
 				}
-			}else if(request instanceof RmDiagramRequest){
+			}else if(request instanceof RmDiagramRequest){ // user un-shared a diagram
 				RmDiagramRequest rdr = (RmDiagramRequest)request;
 				Set<Map.Entry<SocketChannel, Diagram>> entryset = channels.entrySet();
+				SocketChannel channel = null;
 				for(Map.Entry<SocketChannel, Diagram> entry : entryset){
 					if(entry.getValue().getName().equals(rdr.diagramName)){
-						channels.remove(entry.getKey());
-						try{entry.getKey().close();}catch(IOException ioe){ioe.printStackTrace();}
+						channel = entry.getKey();
 					}
 				}
+				if(channel != null)
+					revertDiagram(channel,true);
 			}else if(request instanceof SendCmdRequest||request instanceof SendTreeCmdRequest){
 				SendCmdRequest scr = (SendCmdRequest)request;
 				//System.out.println("ClientConnectionManager:handling request "+scr.cmd.getName());
@@ -541,28 +589,36 @@
 				}catch(IOException e){
 					/* the pending commands is normally removed upon reply receive */
 					pendingCommands.remove(scr);
-					revertDiagram(scr.channel);
+					revertDiagram(scr.channel,false);
 				}
 			}else if(request instanceof SendLockRequest){
 				SendLockRequest slr = (SendLockRequest)request;
 				try {
 					protocol.send(slr.channel,slr.lock);
-					mustAnswer = true;
 				} catch (IOException e) {
-					revertDiagram(slr.channel);
+					revertDiagram(slr.channel,false);
 					try {
-						/* this is to unblock the event dispatching thread, which will then  *
-						 * process the code queued for execution in reverDiagram             */
-						answers.put(new ErrorAnswer());
-					} catch (InterruptedException e1) {
-						throw new RuntimeException(e1); //must never happen
+						/* RevertedDiagramAnswer is to prevent the Event dispatching Thread from blocking if the   *
+						 * server goes down and the client is still waiting for an answer. If the thread *
+						 *  was not waiting for an answers the RevertedDiagramAnswer will not be put in the queue  */
+						if(waitingAnswer)
+							answers.put(new RevertedDiagramAnswer());
+					} catch (InterruptedException ie) {
+						throw new RuntimeException(ie); //must never happen
 					}
 				}
+			}else if(request instanceof SendAwarenessRequest){
+				SendAwarenessRequest awr = (SendAwarenessRequest)request;
+				try{
+					protocol.send(awr.channel, awr.awMsg);
+				}catch (IOException e) {
+					revertDiagram(awr.channel,false);
+				}
 			}
 		}
 	}
 
-	private void revertDiagram(SocketChannel c){
+	private void revertDiagram(SocketChannel c,final boolean userRequest){
 		/* from now on all the commands using this channel will be dropped */
 		final Diagram diagram = channels.remove(c);
 		if(diagram == null)
@@ -577,15 +633,17 @@
 						NetDiagram netDiagram = (NetDiagram)dPanel.getDiagram();
 						if( netDiagram.getDelegate().equals(diagram)){
 							/* set the old (unwrapped) diagram as the current one */
+							dPanel.setAwarenessPanelEnabled(false);
 							dPanel.setDiagram(diagram);
 							break;
 						}
 					}
 				}
-				SpeechOptionPane.showMessageDialog(tabbedPane, MessageFormat.format(
-						ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.connection"),
-						diagram.getName())
-				);
+				if(!userRequest)// show the message only if the revert is due to an error
+					SpeechOptionPane.showMessageDialog(tabbedPane, MessageFormat.format(
+							ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.connection"),
+							diagram.getName())
+					);
 			}
 		});
 		
@@ -603,8 +661,9 @@
 					DiagramPanel dPanel = (DiagramPanel)tabbedPane.getComponentAt(i); 
 					if(dPanel.getDiagram() instanceof NetDiagram){
 						NetDiagram netDiagram = (NetDiagram)dPanel.getDiagram();
-							/* set the old (unwrapped) diagram as the current one */
-							dPanel.setDiagram(netDiagram.getDelegate());
+						/* set the old (unwrapped) diagram as the current one */
+						dPanel.setAwarenessPanelEnabled(false);
+						dPanel.setDiagram(netDiagram.getDelegate());
 					}
 				}
 				SpeechOptionPane.showMessageDialog(
@@ -647,11 +706,11 @@
 	}
 	
 	public static class SendTreeCmdRequest extends SendCmdRequest{		
-		public SendTreeCmdRequest( Command cmd,SocketChannel channel,DiagramModelTreeNode treeNode) {
+		public SendTreeCmdRequest( Command cmd,SocketChannel channel,DiagramTreeNode treeNode) {
 			super(cmd,channel,null);
 			this.treeNode = treeNode;
 		}
-		public DiagramModelTreeNode treeNode;
+		public DiagramTreeNode treeNode;
 		public SocketChannel channel;
 		public Command cmd;
 	}
@@ -665,6 +724,15 @@
 		public LockMessage lock;
 	}
 	
+	public static class SendAwarenessRequest implements Request {
+		public SendAwarenessRequest(SocketChannel channel,  AwarenessMessage awMsg){
+			this.awMsg = awMsg;
+			this.channel = channel;
+		}
+		public SocketChannel channel;
+		public AwarenessMessage awMsg;
+	}
+	
 	public interface Answer {};
 	public static class LockAnswer implements Answer {
 		public LockAnswer(LockMessage answer){
@@ -673,13 +741,11 @@
 		public LockMessage message; 
 	}
 	
-	public static class ErrorAnswer implements Answer{
-		
-	}
+	public static class RevertedDiagramAnswer implements Answer{}
 	
 	private Node node;
 	private Edge edge;
-	private DiagramModelTreeNode treeNode;
+	private DiagramTreeNode treeNode;
 	private Set<Integer> indexes;
 	private SendCmdRequest sendCmdRequest;
 	/* for each server hold the diagram it shares with it */
@@ -690,6 +756,6 @@
 	private Selector selector;
 	private EditorTabbedPane tabbedPane;
 	private Protocol protocol;
-	private boolean mustAnswer;
+	private volatile boolean waitingAnswer;
 	private volatile boolean mustSayGoodbye;
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Command.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Command.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,6 +19,7 @@
 
 package uk.ac.qmul.eecs.ccmi.network;
 
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.utils.CharEscaper;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 
@@ -31,26 +32,32 @@
  *
  */
 public class Command extends Message {
-	public Command(Name name, String diagram, Object[] args, long timestamp){
-		super(timestamp,diagram);
+	public Command(Name name, String diagram, Object[] args, long timestamp, DiagramEventSource source){
+		super(timestamp,diagram,source);
 		this.name = name;
 		this.args = args;
 	}
 	
-	public Command(Name name, String diagram, Object[] args){
-		super(diagram);
+	public Command(Name name, String diagram, Object[] args, DiagramEventSource source){
+		super(diagram,source);
 		this.name = name;
 		this.args = args;
 	}
 	
-	public Command(Name name, String diagram, long timestamp){
-		this(name, diagram);
+	public Command(Name name, String diagram, long timestamp, DiagramEventSource source){
+		this(name, diagram, source);
 	}
 	
-	public Command(Name name, String diagram){
-		this(name, diagram, new Object[]{});
+	public Command(Name name, String diagram, DiagramEventSource source){
+		this(name, diagram, new Object[]{},source);
 	}
 	
+	@Override
+	public DiagramEventSource getSource(){
+		return (DiagramEventSource)super.getSource();
+	}
+	
+	@Override
 	public Name getName() {
 		return name;
 	}
@@ -68,6 +75,11 @@
 	}
 	
 
+	/**
+	 * Utility method to log, through the interaction log, the receipt of a command 
+	 * @param cmd the received command 
+	 * @param action
+	 */
 	public static void log(Command cmd, String action){
 		if(cmd.getName() != Command.Name.LOCAL && cmd.getName() != Command.Name.BEND 
 				&& cmd.getName() != Command.Name.TRANSLATE_EDGE && cmd.getName() != Command.Name.TRANSLATE_NODE){
@@ -90,7 +102,7 @@
 		try {
 			name = Name.valueOf(n);
 		}catch(IllegalArgumentException iae){
-			name.setOrigin(n); 
+			iae.printStackTrace();
 		}
 		return name;
 	}
@@ -122,23 +134,14 @@
 		TRANSLATE_EDGE,
 		BEND,
 		STOP_EDGE_MOVE,
-		STOP_NODE_MOVE;
-		
-		private Name(){
-			origin = null;
-		}
-		
-		private void setOrigin(String origin){
-			this.origin = origin;
-		}
-		
-		@Override
-		public String toString(){
-			if(origin == null)
-				return super.toString();
-			else return origin;
-		}
-		
-		private String origin;
+		STOP_NODE_MOVE,
+		/**
+		 * not a proper command, only used for awareness on node selection for edge creation.
+		 */
+		SELECT_NODE_FOR_EDGE_CREATION,
+		/**
+		 * not a proper command, only used for awareness on node un-selection for edge creation.
+		 */
+		UNSELECT_NODE_FOR_EDGE_CREATION;
 	}	
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/CommandExecutor.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/CommandExecutor.java	Wed Apr 25 17:09:09 2012 +0100
@@ -21,14 +21,20 @@
 
 import java.awt.Component;
 import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.Finder;
 import uk.ac.qmul.eecs.ccmi.gui.GraphElement;
 import uk.ac.qmul.eecs.ccmi.gui.Node;
 import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
@@ -40,44 +46,74 @@
  *
  */
 public abstract class CommandExecutor implements Runnable {
+	protected CommandExecutor(DiagramEventSource source){
+		this.source = source;
+	}
 
+	protected DiagramEventSource source;
 	public static class Insert extends CommandExecutor{
-		public Insert(CollectionModel<Node,Edge> m, DiagramElement de){
+		public Insert(CollectionModel<Node,Edge> m, DiagramElement de, long[] nds, DiagramEventSource source){
+			super(source);
 			element = de;
 			model = m;
+			nodes = nds;
 		}
 		@Override
 		public void run() {
-			if(element instanceof Node)
-				model.insert((Node)element);
-			else
-				model.insert((Edge)element);
+			model.getMonitor().lock();
+			if(element instanceof Node){
+				model.insert((Node)element,source);
+			}else{
+				Edge edge = (Edge)element;
+				if(nodes != null){
+					List<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(nodes.length);
+					/* retrieve the nodes to connect by the id, conveyed in the message */
+					for(int i = 0; i<nodes.length; i++){
+						Node attachedNode = Finder.findNode(nodes[i],model.getNodes());
+						nodesToConnect.add(attachedNode);
+					}
+					try {
+						edge.connect(nodesToConnect);
+					} catch (ConnectNodesException e) {
+						throw new RuntimeException();//this must never happen as the check is done by the local client before issuing the command
+					}
+				}
+				model.insert(edge,source);
+			}
+			model.getMonitor().unlock();
 		}
 		private CollectionModel<Node,Edge> model;
 		private DiagramElement element;
+		private long[] nodes;
 	}
 	
 	public static class Remove extends CommandExecutor{
-		public Remove(CollectionModel<Node,Edge> m, DiagramElement de) {
+		public Remove(CollectionModel<Node,Edge> m, DiagramElement de, DiagramEventSource source) {
+			super(source);
 			model = m;
 			element = de;
 		}
 		@Override
 		public void run() {
-			model.takeOut(element);
+			model.getMonitor().lock();
+			model.takeOut(element,source);
+			model.getMonitor().unlock();
 		}
 		private CollectionModel<Node,Edge> model;
 		private DiagramElement element;
 	}
 	
 	public static class SetName extends CommandExecutor {
-		public SetName(DiagramElement de, String n){
+		public SetName(DiagramElement de, String n, DiagramEventSource source){
+			super(source);
 			element = de;
 			name = n;
 		}
 		@Override
 		public void run(){
-			element.setName(name);
+			element.getMonitor().lock();
+			element.setName(name,source);
+			element.getMonitor().unlock();
 		}
 		private String name;
 		private DiagramElement element;
@@ -85,7 +121,8 @@
 	
 	
 	public static class SetProperty extends CommandExecutor{
-		public SetProperty(Node n, String t, Integer i, String v){
+		public SetProperty(Node n, String t, Integer i, String v, DiagramEventSource source){
+			super(source);
 			node = n;
 			type = t;
 			index = i;
@@ -94,7 +131,9 @@
 		
 		@Override
 		public void run(){
-			node.setProperty(type, index, value);
+			node.getMonitor().lock();
+			node.setProperty(type, index, value,source);
+			node.getMonitor().unlock();
 		}
 		
 		private Node node;
@@ -104,31 +143,40 @@
 	}
 	
 	public static class SetProperties extends CommandExecutor {
-		public SetProperties(Node n, NodeProperties p){
+		public SetProperties(Node n, String p, DiagramEventSource source){
+			super(source);
 			node = n;
-			properties = p;
+			propertiesAsString = p;
 		}
 		@Override
 		public void run(){
-			node.setProperties(properties);
+			node.getMonitor().lock();
+			NodeProperties properties = node.getProperties();
+			properties.fill(propertiesAsString);
+			node.setProperties(properties,source);
+			node.getMonitor().unlock();
 		}
 		private Node node;
-		private NodeProperties properties;
+		private String propertiesAsString;
 	}
 	
 	public static class ClearProperties extends CommandExecutor {
-		public ClearProperties(Node n){
+		public ClearProperties(Node n, DiagramEventSource source){
+			super(source);
 			node = n;
 		}
 		@Override
 		public void run(){
-			node.clearProperties();
+			node.getMonitor().lock();
+			node.clearProperties(source);
+			node.getMonitor().unlock();
 		}
 		private Node node;
 	}
 	
 	public static class SetNotes extends CommandExecutor{
-		public SetNotes(TreeModel<Node,Edge>  m, DiagramModelTreeNode tn, String n){
+		public SetNotes(TreeModel<Node,Edge>  m, DiagramTreeNode tn, String n, DiagramEventSource source){
+			super(source);
 			model = m;
 			treeNode = tn;
 			notes = n;
@@ -136,22 +184,27 @@
 		
 		@Override
 		public void run(){
-			model.setNotes(treeNode, notes);
+			model.getMonitor().lock();
+			model.setNotes(treeNode, notes,source);
+			model.getMonitor().unlock();
 		}
-		private DiagramModelTreeNode treeNode;
+		private DiagramTreeNode treeNode;
 		private String notes;
 		private TreeModel<Node,Edge>  model;
 	}
 	
 	public static class AddProperty extends CommandExecutor {
-		public AddProperty(Node n, String t, String v){
+		public AddProperty(Node n, String t, String v, DiagramEventSource source){
+			super(source);
 			node = n;
 			type = t;
 			value = v;
 		}
 		@Override
 		public void run(){
-			node.addProperty(type, value);
+			node.getMonitor().lock();
+			node.addProperty(type, value,source);
+			node.getMonitor().unlock();
 		}
 		private Node node;
 		private String type;
@@ -159,7 +212,8 @@
 	}
 	
 	public static class RemoveProperty extends CommandExecutor {
-		public RemoveProperty(Node n, String t, int i){
+		public RemoveProperty(Node n, String t, int i, DiagramEventSource source){
+			super(source);
 			node = n;
 			type = t;
 			index = i;
@@ -167,7 +221,9 @@
 		
 		@Override
 		public void run(){
-			node.removeProperty(type, index);
+			node.getMonitor().lock();
+			node.removeProperty(type, index,source);
+			node.getMonitor().unlock();
 		}
 		
 		private Node node;
@@ -176,7 +232,8 @@
 	}
 	
 	public static class SetModifiers extends CommandExecutor {
-		public SetModifiers(Node n,String t,Integer v, Set<Integer> i){
+		public SetModifiers(Node n,String t,Integer v, Set<Integer> i, DiagramEventSource source){
+			super(source);
 			node = n;
 			type = t;
 			value = v;
@@ -184,7 +241,9 @@
 		}
 		@Override
 		public void run(){
-			node.setModifierIndexes(type, value, indexes);
+			node.getMonitor().lock();
+			node.setModifierIndexes(type, value, indexes,source);
+			node.getMonitor().unlock();
 		}
 		private Node node;
 		private String type;
@@ -193,14 +252,17 @@
 	}
 	
 	public static class SetEndLabel extends CommandExecutor {
-		public SetEndLabel(Edge e, Node n, String l){
+		public SetEndLabel(Edge e, Node n, String l, DiagramEventSource source){
+			super(source);
 			edge = e;
 			node = n;
 			label = l;
 		}
 		@Override
 		public void run(){
-			edge.setEndLabel(node, label);
+			edge.getMonitor().lock();
+			edge.setEndLabel(node, label,source);
+			edge.getMonitor().unlock();
 		}
 		private Node node;
 		private Edge edge;
@@ -208,14 +270,17 @@
 	}
 	
 	public static class SetEndDescription extends CommandExecutor {
-		public SetEndDescription(Edge e, Node n, int i){
+		public SetEndDescription(Edge e, Node n, int i, DiagramEventSource source){
+			super(source);
 			edge = e;
 			node = n;
 			index = i;
 		}
 		@Override
 		public void run(){
-			edge.setEndDescription(node, index);
+			edge.getMonitor().lock();
+			edge.setEndDescription(node, index,source);
+			edge.getMonitor().unlock();
 		}
 		private Node node;
 		private Edge edge;
@@ -223,7 +288,8 @@
 	}
 	
 	public static class Translate extends CommandExecutor {
-		public Translate(GraphElement ge, Point2D p, Double x, Double y){
+		public Translate(GraphElement ge, Point2D p, Double x, Double y, DiagramEventSource source){
+			super(source);
 			element = ge;
 			point = p;
 			dx = x;
@@ -232,7 +298,15 @@
 		
 		@Override
 		public void run(){
-			element.translate(point, dx, dy);
+			if(element instanceof Node)
+				((Node)element).getMonitor().lock();
+			else
+				((Edge)element).getMonitor().lock();
+			element.translate(point, dx, dy,source);
+			if(element instanceof Node)
+				((Node)element).getMonitor().unlock();
+			else
+				((Edge)element).getMonitor().unlock();
 		}
 		
 		private GraphElement element;
@@ -242,29 +316,41 @@
 	}
 	
 	public static class StartMove extends CommandExecutor {
-		public StartMove(GraphElement ge, Point2D p){
+		public StartMove(GraphElement ge, Point2D p, DiagramEventSource source){
+			super(source);
 			element = ge;
 			point = p;
 		}
 		@Override
 		public void run(){
-			element.startMove(point);
+			if(element instanceof Node)
+				((Node)element).getMonitor().lock();
+			else
+				((Edge)element).getMonitor().lock();
+			element.startMove(point,source);
+			if(element instanceof Node)
+				((Node)element).getMonitor().unlock();
+			else
+				((Edge)element).getMonitor().unlock();
 		}
 		private GraphElement element;
 		private Point2D point;
 	}
 	
 	public static class Bend extends CommandExecutor {
-		public Bend(Edge e, Point2D p, Point2D bs){
+		public Bend(Edge e, Point2D p, Point2D bs, DiagramEventSource source){
+			super(source);
 			edge = e;
 			point = p;
 			bendStart = bs;
 		}
 		@Override
 		public void run(){
+			edge.getMonitor().lock();
 			if(bendStart != null)
-				edge.startMove(bendStart);
-			edge.bend(point);
+				edge.startMove(bendStart,source);
+			edge.bend(point,source);
+			edge.getMonitor().unlock();
 		}
 		private Edge edge;
 		private Point2D point;
@@ -272,18 +358,28 @@
 	}
 	
 	public static class StopMove extends CommandExecutor {
-		public StopMove(GraphElement ge){
+		public StopMove(GraphElement ge, DiagramEventSource source){
+			super(source);
 			element = ge;
 		}
 		@Override
 		public void run(){
-			element.stopMove();
+			if(element instanceof Node)
+				((Node)element).getMonitor().lock();
+			else
+				((Edge)element).getMonitor().lock();
+			element.stopMove(source);
+			if(element instanceof Node)
+				((Node)element).getMonitor().unlock();
+			else
+				((Edge)element).getMonitor().unlock();
 		}
 		private GraphElement element;
 	}
 	
 	public static class ShowErrorMessageDialog extends CommandExecutor {
-		public ShowErrorMessageDialog(Component c, String msg){
+		public ShowErrorMessageDialog(Component c, String msg, DiagramEventSource source){
+			super(source);
 			message = msg;
 			parentComponent = c; 
 		}
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/DiagramDownloader.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/DiagramDownloader.java	Wed Apr 25 17:09:09 2012 +0100
@@ -23,6 +23,7 @@
 import java.net.InetSocketAddress;
 import java.nio.channels.SocketChannel;
 
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
 
@@ -37,10 +38,10 @@
  */
 public class DiagramDownloader extends SpeechOptionPane.ProgressDialogWorker<String,Void> {
 
-	public DiagramDownloader(SocketChannel channel, String target, Task task){
+	public DiagramDownloader(SocketChannel channel, String target, int task){
 		this.channel = channel;
 		this.task = task;
-		if(task == Task.CONNECT_AND_DOWNLOAD_LIST)
+		if(task == CONNECT_AND_DOWNLOAD_LIST_TASK)
 			this.address = target;
 		else
 			this.diagramName = target;
@@ -48,18 +49,18 @@
 	
 	@Override
 	protected String doInBackground() throws Exception {
-		if(task == Task.CONNECT_AND_DOWNLOAD_LIST){
+		if(task == CONNECT_AND_DOWNLOAD_LIST_TASK){
 			int port = Integer.parseInt(PreferencesService.getInstance().get("server.remote_port", Server.DEFAULT_REMOTE_PORT));
 			channel.connect(new InetSocketAddress(address,port));
 		}
 		
 		Protocol protocol = ProtocolFactory.newInstance();
 		switch(task){
-			case CONNECT_AND_DOWNLOAD_LIST : 
-				protocol.send(channel, new Command(Command.Name.LIST,""));
+			case CONNECT_AND_DOWNLOAD_LIST_TASK : 
+				protocol.send(channel, new Command(Command.Name.LIST,"",DiagramEventSource.NONE));
 				break;
-			case DOWNLOAD_DIAGRAM :	
-				protocol.send(channel, new Command(Command.Name.GET ,diagramName));
+			case DOWNLOAD_DIAGRAM_TASK :	
+				protocol.send(channel, new Command(Command.Name.GET ,diagramName,DiagramEventSource.NONE));
 		}
 		Reply reply = protocol.receiveReply(channel);
 		switch(reply.getName()){
@@ -76,13 +77,11 @@
 		}
 	}
 	
-	public static enum Task{
-		CONNECT_AND_DOWNLOAD_LIST,
-		DOWNLOAD_DIAGRAM;
-	};
+	public static final int CONNECT_AND_DOWNLOAD_LIST_TASK = 0;
+	public static final int DOWNLOAD_DIAGRAM_TASK = 1;
 
 	private SocketChannel channel;
 	private String diagramName;
 	private String address;
-	private Task task;
+	private int task;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/DiagramEventActionSource.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,151 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/ 
+package uk.ac.qmul.eecs.ccmi.network;
+
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
+
+/**
+ * This class represent a source of an editing action. An editing action is initiated when 
+ * the user gets the lock on a certain element and terminates when the user after changing 
+ * the diagram model somehow yields the lock back to the server.   
+ *
+ */
+public class DiagramEventActionSource extends DiagramEventSource{
+
+	public long getTimestamp() {
+		return timestamp;
+	}
+
+	public void setTimestamp(long timestamp) {
+		this.timestamp = timestamp;
+	}
+
+	public DiagramEventActionSource (DiagramEventSource eventSource, Command.Name cmd, long elementID, String elementName) {
+		super(eventSource);
+		this.cmd = cmd;
+		this.elementID = elementID;
+		this.saveID = elementID; 
+		this.elementName = elementName;
+		userName = AwarenessMessage.getDefaultUserName();
+		timestamp = System.currentTimeMillis();
+	}
+
+	public Command.Name getCmd() {
+		return cmd;
+	}
+	
+	public long getElementID(){
+		return elementID;
+	}
+	
+	public void setElementID(long ID){
+		elementID = ID;
+	}
+	
+	long getSaveID(){
+		return saveID;
+	}
+	
+	public String getElementName(){
+		return elementName;
+	}
+	
+	public void setElementName(String elementName){
+		this.elementName = elementName;
+	}
+	
+	public void setUserName(String name){
+		this.userName = name;
+	}
+	
+	public String getUserName(){
+		return userName;
+	}
+	
+	/**
+	 * 
+	 * The local user never gets this informations from itself are they are attached to 
+	 * AwernessMessages they receive only from other users. Therefore instances of this class
+	 * are never considered local. 
+	 */
+	@Override
+	public boolean isLocal(){
+		return false;
+	}
+	
+	/**
+	 * Returns an instance of {@code DiagramEventActionSource} out of a 
+	 * String passed as argument
+	 * @param s a string representation of a  {@code DiagramEventActionSource} instance, as 
+	 * returned by toString.  
+	 * @return an instance of  {@code DiagramEventActionSource}
+	 */
+	public static DiagramEventActionSource valueOf(String s){
+		if(s.isEmpty())
+			return NULL;
+		String[] strings = s.split(SEPARATOR); 
+		long id = Long.parseLong(strings[1]);
+		String elementName = strings[2];
+		long timestamp = Long.parseLong(strings[3]);
+		DiagramEventSource eventSource = DiagramEventSource.valueOf(strings[5]);
+		DiagramEventActionSource toReturn = new DiagramEventActionSource(eventSource,Command.Name.valueOf(strings[0]),id,elementName);
+		toReturn.setUserName(strings[4]);
+		toReturn.setTimestamp(timestamp);
+		return toReturn;
+	}
+
+	/**
+	 * Encodes this object into a String. the encoding is done by concatenating the command name 
+	 * with the string representation of the event source. the command name is encoded in a fixed length
+	 * string and padded with white spaces if such length is greater than the command name's.
+	 */
+	@Override
+	public String toString(){
+		StringBuilder builder = new StringBuilder(cmd.name());
+		builder.append(SEPARATOR);
+		builder.append(elementID);
+		builder.append(SEPARATOR);
+		builder.append(elementName);
+		builder.append(SEPARATOR);
+		builder.append(timestamp);
+		builder.append(SEPARATOR);
+		builder.append(userName);
+		builder.append(SEPARATOR);
+		builder.append(super.toString());
+		return builder.toString();
+	}
+	
+	
+	
+	private String userName;
+	private Command.Name cmd;
+	private long elementID;
+	private long saveID;
+	private String elementName;
+	private static final String SEPARATOR = "\n";
+	private long timestamp;
+	
+	public static DiagramEventActionSource NULL = new DiagramEventActionSource(DiagramEventSource.NONE,Command.Name.NONE,-1,""){
+		@Override
+		public String toString(){
+			return "";
+		}
+	};
+	
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/LockMessage.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/LockMessage.java	Wed Apr 25 17:09:09 2012 +0100
@@ -36,8 +36,8 @@
 	/**
 	 * Creates a lock message for the given diagram and with the given timestamp.
 	 */
-	public LockMessage(Name name, long timestamp, String diagram, Object[] args) {
-		super(timestamp, diagram);
+	public LockMessage(Name name, long timestamp, String diagram, Object[] args, Object source) {
+		super(timestamp, diagram,source);
 		this.args = args;
 		this.name = name;
 	}
@@ -46,18 +46,18 @@
 	 * Creates a lock message for the given diagram and timestamp of the moment 
 	 * the message is created.
 	 */
-	public LockMessage(Name name, String diagram, Object[] args) {
-		super(diagram);
+	public LockMessage(Name name, String diagram, Object[] args, DiagramEventActionSource source) {
+		super(diagram,source);
 		this.args = args;
 		this.name = name;
 	}
 	
-	public LockMessage(Name name, long timestamp, String diagram, long id) {
-		this(name,timestamp,diagram,new Object[]{id});
+	public LockMessage(Name name, long timestamp, String diagram, long id, DiagramEventActionSource source) {
+		this(name,timestamp,diagram,new Object[]{id},source);
 	}
 	
-	public LockMessage(Name name, String diagram, long id){
-		this(name,diagram,new Object[]{id});
+	public LockMessage(Name name, String diagram, long id, DiagramEventActionSource source){
+		this(name,diagram,new Object[]{id},source);
 	}
 	
 	@Override
@@ -77,18 +77,24 @@
 		return args.length;
 	}
 	
+	@Override
+	public DiagramEventActionSource getSource(){
+		return (DiagramEventActionSource)super.getSource();
+	}
+	
 	public static LockMessage.Name valueOf(String n){
 		Name name = Name.NONE_L;
 		try {
 			name = Name.valueOf(n);
 		}catch(IllegalArgumentException iae){
-			name.setOrigin(n); 
+			iae.printStackTrace();
+			return Name.NONE_L; 
 		}
 		return name;
 	}
 
 	/** used to distinguish between different kinds of messages. */
-	public static final String LOCK_NAME_POSTFIX = "_L";
+	public static final String NAME_POSTFIX = "_L";
 	public static final String GET_LOCK_PREFIX = "GET_";
 	public static final String YIELD_LOCK_PREFIX = "YIELD_";
 	private Name name;
@@ -120,22 +126,5 @@
 		YES_L,
 		NO_L,
 		NONE_L;
-		
-		private Name(){
-			origin = null;
-		}
-		
-		private void setOrigin(String origin){
-			this.origin = origin;
-		}
-		
-		@Override
-		public String toString(){
-			if(origin == null)
-				return super.toString();
-			else return origin;
-		}
-		
-		private String origin;
 	}
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Message.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Message.java	Wed Apr 25 17:09:09 2012 +0100
@@ -25,13 +25,14 @@
  */
 public abstract class Message {
 	
-	public Message(long timestamp, String diagram){
+	public Message(long timestamp, String diagram, Object source){
 		this.timestamp = timestamp;
 		this.diagram = diagram;
+		this.source = source;
 	}
 	
-	public Message(String diagram){
-		this(System.currentTimeMillis(),diagram);
+	public Message(String diagram, Object source){
+		this(System.currentTimeMillis(),diagram,source);
 	}
 	
 	public long getTimestamp() {
@@ -42,10 +43,19 @@
 		return diagram;
 	}
 	
+	public Object getSource(){
+		return source;
+	}
+	
+	public void setSource(Object src){
+		source = src;
+	}
+	
 	public abstract MessageName getName();
 	
 	private long timestamp;
 	private String diagram;
+	private Object source;
 	
 	public static interface MessageName {
 		String toString();
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/NetDiagram.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/NetDiagram.java	Wed Apr 25 17:09:09 2012 +0100
@@ -30,15 +30,17 @@
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
 import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.gui.DiagramModelUpdater;
 import uk.ac.qmul.eecs.ccmi.gui.Edge;
 import uk.ac.qmul.eecs.ccmi.gui.GraphElement;
 import uk.ac.qmul.eecs.ccmi.gui.Lock;
 import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.AwarenessPanel;
 import uk.ac.qmul.eecs.ccmi.gui.persistence.PrototypePersistenceDelegate;
 import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.LockAnswer;
 import uk.ac.qmul.eecs.ccmi.utils.ExceptionHandler;
@@ -58,6 +60,7 @@
 	
 	private NetDiagram(Diagram delegateDiagram){
 		this.delegateDiagram = delegateDiagram;
+		 innerModelUpdater = new InnerModelUpdater();
 	}
 	
 	public static NetDiagram wrapRemoteHost(Diagram diagram, ClientConnectionManager connectionManager,SocketChannel channel){
@@ -117,25 +120,31 @@
 		return delegateDiagram;
 	}
 	
+	public abstract void enableAwareness(AwarenessPanel panel);
+	
+	public abstract void disableAwareness(AwarenessPanel panel);
+		
 	public abstract SocketChannel getSocketChannel();
 	
 	protected abstract void send(Command cmd, DiagramElement element);
 	
-	protected abstract void send(Command cmd, DiagramModelTreeNode treeNode);
+	protected abstract void send(Command cmd, DiagramTreeNode treeNode);
 	
 	protected abstract void send(LockMessage lockMessage);
 	
+	protected abstract void send(AwarenessMessage awMsg);
+	
 	protected abstract boolean receiveLockAnswer();
 	
 	private Diagram delegateDiagram;
-	private InnerModelUpdater innerModelUpdater = new InnerModelUpdater(); 
+	private InnerModelUpdater innerModelUpdater;
+	public static String LOCALHOST_STRING = " @ localhost";
 	
 	private class InnerModelUpdater implements DiagramModelUpdater {
-		
 		@Override
-		public boolean getLock(DiagramModelTreeNode treeNode, Lock lock) {
+		public boolean getLock(DiagramTreeNode treeNode, Lock lock, DiagramEventActionSource actionSource) {
 			try {
-				sendLockMessage(treeNode,lock,true);
+				sendLockMessage(treeNode,lock,true,actionSource);
 			}catch(IllegalArgumentException iae){
 				return false;
 			}
@@ -143,13 +152,22 @@
 		}
 		
 		@Override
-		public void yieldLock(DiagramModelTreeNode treeNode, Lock lock) {
+		public void yieldLock(DiagramTreeNode treeNode, Lock lock, DiagramEventActionSource actionSource) {
 			try {
-				sendLockMessage(treeNode,lock,false);
+				sendLockMessage(treeNode,lock,false,actionSource);
 			}catch(IllegalArgumentException iae) {}
 		}
 		
-		private void sendLockMessage(DiagramModelTreeNode treeNode, Lock lock, boolean isGettingLock){
+		@Override 
+		public void sendAwarenessMessage(AwarenessMessage.Name awMsgName, Object source){
+			if(source instanceof DiagramEventActionSource)
+				send(new AwarenessMessage(awMsgName,getName(),(DiagramEventActionSource)source));
+			else if(source instanceof String){
+				send(new AwarenessMessage(awMsgName,getName(),(String)source));
+			}
+		}
+		
+		private void sendLockMessage(DiagramTreeNode treeNode, Lock lock, boolean isGettingLock, DiagramEventActionSource source){
 			TreeNode[] path = treeNode.getPath();
 			Object[] args = new Object[path.length-1];
 			if(args.length == 0 && !treeNode.isRoot())
@@ -160,22 +178,25 @@
 			send(new LockMessage(
 					LockMessageConverter.getLockMessageNamefromLock(lock,isGettingLock),
 					delegateDiagram.getName(),
-					args
+					args,
+					source
 			));
 		}
 
 		@Override
-		public void insertInCollection(DiagramElement element) {
+		public void insertInCollection(DiagramElement element,DiagramEventSource source) {
 			boolean isNode = false;
 			if(element instanceof Node)
 				isNode = true;
+			
 			Command cmd = null;
 			if(isNode){
 				Rectangle2D bounds = ((Node)element).getBounds();
 				cmd = new Command(
 					Command.Name.INSERT_NODE,
 					delegateDiagram.getName(),		
-					new Object[] {element.getType(),bounds.getX(),bounds.getY()}
+					new Object[] {element.getType(),bounds.getX(),bounds.getY()},
+					makeRemote(source)
 				);
 			}else{
 				Edge edge = (Edge)element;
@@ -187,7 +208,8 @@
 				cmd = new Command(
 						Command.Name.INSERT_EDGE,
 						delegateDiagram.getName(),
-						args
+						args,
+						makeRemote(source)
 				);
 			}
 			send(cmd,element);
@@ -195,97 +217,104 @@
 
 		@Override
 		public void insertInTree(DiagramElement element) {
-			insertInCollection(element);
+			insertInCollection(element,DiagramEventSource.TREE);
 		}
 
 		@Override
-		public void takeOutFromCollection(DiagramElement element) {
+		public void takeOutFromCollection(DiagramElement element,DiagramEventSource source) {
 			boolean isNode = false;
 			if(element  instanceof Node)
 				isNode = true;
 			Command cmd = new Command(
 					isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
 					delegateDiagram.getName(),
-					new Object[] {element.getId()}
+					new Object[] {element.getId()},
+					makeRemote(source)	
 			);
 			send(cmd,element);
 		}
 
 		@Override
 		public void takeOutFromTree(DiagramElement element) {
-			takeOutFromCollection(element);
+			takeOutFromCollection(element,DiagramEventSource.TREE);
 		}
 
 		@Override
-		public void setName(DiagramElement element, String name) {
+		public void setName(DiagramElement element, String name, DiagramEventSource source) {
 			send(new Command(
 					element instanceof Node ? Command.Name.SET_NODE_NAME : Command.Name.SET_EDGE_NAME, 
 					delegateDiagram.getName(),
-					new Object[] {element.getId(), name}),
+					new Object[] {element.getId(), name},
+					makeRemote(source)),
 				 element);	
 		}
 
 		@Override
-		public void setNotes(DiagramModelTreeNode treeNode, String notes) {
+		public void setNotes(DiagramTreeNode treeNode, String notes, DiagramEventSource source) {
 			TreeNode[] path = treeNode.getPath();
 			Object[] args = new Object[path.length];
 			for(int i=0;i<path.length-1;i++){
 				args[i] = delegateDiagram.getTreeModel().getIndexOfChild(path[i], path[i+1]);
 			}
 			args[args.length-1] = notes;
-			Command cmd = new Command(Command.Name.SET_NOTES, delegateDiagram.getName(),args);
+			Command cmd = new Command(Command.Name.SET_NOTES, delegateDiagram.getName(),args,makeRemote(source));
 			send(cmd,treeNode);
 		}
 
 		@Override
-		public void setProperty(Node node, String type, int index, String value) {
+		public void setProperty(Node node, String type, int index, String value, DiagramEventSource source) {
 			send(new Command(Command.Name.SET_PROPERTY, 
 					delegateDiagram.getName(),
-					new Object[] {node.getId(),type,index,value}), 
-				 node
+					new Object[] {node.getId(),type,index,value}, 
+					makeRemote(source)),
+				node
 			);
 		}
 
 		@Override
-		public void setProperties(Node node, NodeProperties properties) {
+		public void setProperties(Node node, NodeProperties properties, DiagramEventSource source) {
 			send(new Command(Command.Name.SET_PROPERTIES, 
 					delegateDiagram.getName(),
-					new Object[] {node.getId(),properties.toString()}),
+					new Object[] {node.getId(),properties.toString()},
+					makeRemote(source)),
 				node	
 			);
 			
 		}
 
 		@Override
-		public void clearProperties(Node node) {
+		public void clearProperties(Node node, DiagramEventSource source) {
 			send(new Command(Command.Name.CLEAR_PROPERTIES,
 					delegateDiagram.getName(),
-					new Object[] {node.getId()}),
+					new Object[] {node.getId()},
+					makeRemote(source)),
 				node	
 			);
 		}
 
 		@Override
-		public void addProperty(Node node, String type, String value) {
+		public void addProperty(Node node, String type, String value, DiagramEventSource source) {
 			send(new Command(Command.Name.ADD_PROPERTY,
 					delegateDiagram.getName(),
-					new Object[] {node.getId(),type,value}),
+					new Object[] {node.getId(),type,value},
+					makeRemote(source)),
 				node	
 			);
 		}
 
 		@Override
-		public void removeProperty(Node node, String type, int index) {
+		public void removeProperty(Node node, String type, int index, DiagramEventSource source) {
 			send(new Command(Command.Name.REMOVE_PROPERTY, 
 					delegateDiagram.getName(),
-					new Object[] {node.getId(),type,index}),
+					new Object[] {node.getId(),type,index},
+					makeRemote(source)),
 				node	
 			);
 		}
 
 		@Override
 		public void setModifiers(Node node, String type, int index,
-				Set<Integer> modifiers) {
+				Set<Integer> modifiers, DiagramEventSource source) {
 			Object args[] = new Object[modifiers.size()+3];
 			args[0] = node.getId();
 			args[1] = type;
@@ -295,27 +324,27 @@
 				args[i+3] = I;
 				i++;
 			}
-			send(new Command(Command.Name.SET_MODIFIERS, delegateDiagram.getName(),args),node);
+			send(new Command(Command.Name.SET_MODIFIERS, delegateDiagram.getName(),args,makeRemote(source)),node);
 		}
 
 		@Override
-		public void setEndLabel(Edge edge, Node node, String label) {
+		public void setEndLabel(Edge edge, Node node, String label, DiagramEventSource source) {
 			send(new Command(Command.Name.SET_ENDLABEL, delegateDiagram.getName(),
-					new Object[] {edge.getId(), node.getId(), label}),
+					new Object[] {edge.getId(), node.getId(), label},makeRemote(source)),
 				edge
 			);
 		}
 
 		@Override
-		public void setEndDescription(Edge edge, Node node, int index) {
+		public void setEndDescription(Edge edge, Node node, int index, DiagramEventSource source) {
 			send(new Command(Command.Name.SET_ENDDESCRIPTION, delegateDiagram.getName(),
-					new Object[] {edge.getId(), node.getId(), index}),
+					new Object[] {edge.getId(), node.getId(), index},makeRemote(source)),
 				edge
 			);
 		}
 
 		@Override
-		public void translate(GraphElement ge, Point2D p, double dx, double dy) {
+		public void translate(GraphElement ge, Point2D p, double dx, double dy, DiagramEventSource source) {
 			double px = 0;
 			double py = 0;
 			if(p != null){
@@ -325,18 +354,18 @@
 			if(ge instanceof Node){
 				Node n = (Node)ge;
 				send(new Command(Command.Name.TRANSLATE_NODE, delegateDiagram.getName(),
-							new Object[] {n.getId(), px, py, dx,dy}
+							new Object[] {n.getId(), px, py, dx,dy},makeRemote(source)
 							),n);
 			}else{
 				Edge e = (Edge)ge;
 				send(new Command(Command.Name.TRANSLATE_EDGE, delegateDiagram.getName(),
-						new Object[] {e.getId(), px, py, dx,dy}
+						new Object[] {e.getId(), px, py, dx,dy},makeRemote(source)
 						),e);
 			}
 		}
 
 		@Override
-		public void startMove(GraphElement ge, Point2D p) {
+		public void startMove(GraphElement ge, Point2D p, DiagramEventSource source) {
 			/* Store internally the point the motion started from and send a unique message     *
 			 * to the server when the edge is actually bended. This is because the lock will be *
 			 * asked only when the mouse motion actually starts, whereas this call is done when * 
@@ -346,35 +375,46 @@
 		}
 			
 		@Override
-		public void bend(Edge edge, Point2D p) {
+		public void bend(Edge edge, Point2D p, DiagramEventSource source) {
 			/* send informations about the starting point only at the first time */
 			if(edgeStartMovePoint == null)
 				send(new Command(Command.Name.BEND, delegateDiagram.getName(),
-						new Object[] {edge.getId(),p.getX(),p.getY()}),
-						edge);
+						new Object[] {edge.getId(),p.getX(),p.getY()},
+						makeRemote(source)),
+					edge);
 			else{
 				send(new Command(Command.Name.BEND, delegateDiagram.getName(),
-						new Object[] {edge.getId(),p.getX(),p.getY(),edgeStartMovePoint.getX(),edgeStartMovePoint.getY()}),
+						new Object[] {edge.getId(),p.getX(),p.getY(),
+										edgeStartMovePoint.getX(),edgeStartMovePoint.getY()},
+										makeRemote(source)),
 						edge);
 				edgeStartMovePoint = null;
 			}
 		}
 
 		@Override
-		public void stopMove(GraphElement ge) {
+		public void stopMove(GraphElement ge, DiagramEventSource source) {
 			if(ge instanceof Node){
 				Node n = (Node)ge;
 				send(new Command(Command.Name.STOP_NODE_MOVE, delegateDiagram.getName(),
-						new Object[] {n.getId()}),
+						new Object[] {n.getId()},makeRemote(source)),
 					n);
 			}else{
 				Edge e = (Edge)ge;
 				send(new Command(Command.Name.STOP_EDGE_MOVE, delegateDiagram.getName(),
-					new Object[] {e.getId()}),
+					new Object[] {e.getId()},makeRemote(source)),
 					e);
 			}
 		}
 		
+		/* source passed as argument to the updater methods have are local and with no id 
+		 * since this source has to be sent to the server, it must be set as non local 
+		 * (constructor will do) and the is must be set as well 
+		 */
+		private DiagramEventSource makeRemote(DiagramEventSource src){
+			return new DiagramEventSource(src);
+		}
+		
 		private Point2D edgeStartMovePoint;
 	}
 	
@@ -400,7 +440,7 @@
 		}
 		
 		@Override
-		protected void send(Command cmd, DiagramModelTreeNode treeNode) {
+		protected void send(Command cmd, DiagramTreeNode treeNode) {
 			connectionManager.addRequest(new ClientConnectionManager.SendTreeCmdRequest(cmd, channel, treeNode ));
 		}
 		
@@ -410,10 +450,17 @@
 		}
 		
 		@Override
+		protected void send(AwarenessMessage awMsg){
+			connectionManager.addRequest(new ClientConnectionManager.SendAwarenessRequest(channel,awMsg));
+		}
+		
+		@Override
 		protected boolean receiveLockAnswer(){
 			ClientConnectionManager.Answer answer = connectionManager.getAnswer();
-			if(answer instanceof ClientConnectionManager.ErrorAnswer)
-				return false;
+			/* diagram has been reverted while waiting for a lock answer : the answer is gonna be yes         *
+			 * then, as the client is no longer connected to the server and there is no more locking in place */
+			if(answer instanceof ClientConnectionManager.RevertedDiagramAnswer)
+				return true;
 			LockMessage.Name name = ((LockAnswer)answer).message.getName();
 			switch(name){
 			case YES_L :
@@ -439,6 +486,16 @@
 		}
 		
 		@Override
+		public void enableAwareness(AwarenessPanel panel){
+			connectionManager.getAwarenessPanelEditor().addAwarenessPanel(panel);
+		}
+		
+		@Override
+		public void disableAwareness(AwarenessPanel panel){
+			connectionManager.getAwarenessPanelEditor().removeAwarenessPanel(panel);
+		}
+		
+		@Override
 		public Object clone(){
 			throw new UnsupportedOperationException();
 		}
@@ -489,7 +546,16 @@
 			} catch (IOException ioe) {
 				exceptionHandler.handleException(ioe);
 			}
-		}		
+		}
+		
+		@Override
+		protected void send(AwarenessMessage awMsg){
+			try {
+				protocol.send(channel, awMsg);
+			} catch (IOException ioe) {
+				exceptionHandler.handleException(ioe);
+			}
+		}
 		
 		@Override
 		protected boolean receiveLockAnswer(){
@@ -511,7 +577,7 @@
 		}
 		
 		@Override
-		protected void send(Command cmd , DiagramModelTreeNode treeNode){
+		protected void send(Command cmd , DiagramTreeNode treeNode){
 			try {
 				protocol.send(channel, cmd);
 			} catch (IOException ioe) {
@@ -521,7 +587,7 @@
 		
 		@Override 
 		public String getLabel(){
-			return  getName()+" @ localhost";
+			return  getName()+LOCALHOST_STRING;
 		}
 		
 		@Override
@@ -529,8 +595,18 @@
 			return channel;
 		}
 		
-		SocketChannel channel;
-		Queue<DiagramElement> diagramElements;
+		@Override
+		public void enableAwareness(AwarenessPanel panel){
+			Server.getServer().getAwarenessPanelEditor().addAwarenessPanel(panel);
+		}
+		
+		@Override
+		public void disableAwareness(AwarenessPanel panel){
+			Server.getServer().getAwarenessPanelEditor().removeAwarenessPanel(panel);
+		}
+		
+		private SocketChannel channel;
+		private Queue<DiagramElement> diagramElements;
 		private Protocol protocol;
 		private ExceptionHandler exceptionHandler;
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/NetworkThread.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,40 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.network;
+
+import uk.ac.qmul.eecs.ccmi.gui.awareness.AwarenessPanelEditor;
+
+class NetworkThread extends Thread {
+
+	public NetworkThread() {
+		super();
+		panelEditor = new AwarenessPanelEditor();
+	}
+
+	public NetworkThread(String name) {
+		super(name);
+		panelEditor = new AwarenessPanelEditor();
+	}
+	
+	public AwarenessPanelEditor getAwarenessPanelEditor(){
+		return panelEditor;
+	}
+	
+	private AwarenessPanelEditor panelEditor;
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/OscProtocol.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/OscProtocol.java	Wed Apr 25 17:09:09 2012 +0100
@@ -23,6 +23,9 @@
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.nio.channels.SocketChannel;
+import java.util.ResourceBundle;
+
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 
 import de.sciss.net.OSCBundle;
 import de.sciss.net.OSCMessage;
@@ -43,16 +46,19 @@
 	public void send(SocketChannel channel, Command cmd) throws IOException {
 		/* OSC message args = [diagram, cmd.arg1, cmd.arg2, amd.arg2, ... , cmd.argN  ] */
 		bundle = new CCmIOSCBundle(cmd.getTimestamp());
-		Object[] args = new Object[1+cmd.getArgNum()];
+		Object[] args = new Object[2+cmd.getArgNum()];
 		args[0] = cmd.getDiagram();
+		args[1] = cmd.getSource().toString();
 		for(int i=0; i<cmd.getArgNum();i++)
-			args[i+1] = cmd.getArgAt(i);
+			args[i+2] = cmd.getArgAt(i);
 		
 		bundle.addPacket(new OSCMessage(OSC_NAME_PREFIX+cmd.getName().toString(),args));
 		try{
 			writeBundle(channel);
 		}catch(IOException u){
-			throw new IOException("Could not send data to the server:<" + System.identityHashCode(channel)+">",u);
+			throw new IOException(
+					/* give a more user friendly message */
+					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
 		}
 	}
 	
@@ -62,56 +68,77 @@
 		bundle = new CCmIOSCBundle(reply.getTimestamp());
 		bundle.addPacket(new OSCMessage(
 				OSC_NAME_PREFIX+reply.getName().toString(), 
-				new Object[] {reply.getMessageLen(), reply.getDiagram() ,reply.getMessage()}
-				)
-		);
+				new Object[] {reply.getMessageLen(), reply.getDiagram() ,reply.getMessage(),reply.getSource().toString()}
+				));
 		try{
 			writeBundle(channel);
 		}catch(IOException u){
-			throw new IOException("Could not send data to the server",u);
+			throw new IOException(
+					/* give a more user friendly message */
+					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
 		}
 	}
 	
 	@Override
 	public void send(SocketChannel channel, LockMessage lockMessage) throws IOException {
-		/* OSC message args = [diagram, lock.arg1, lock.arg2, lock.arg2, ... , lock.argN  ] */
+		/* OSC message args = [diagram, source, lock.arg1, lock.arg2, lock.arg2, ... , lock.argN  ] */
 		bundle = new CCmIOSCBundle(lockMessage.getTimestamp());
-		Object[] args = new Object[1+lockMessage.getArgNum()];
+		Object[] args = new Object[2+lockMessage.getArgNum()];
 		args[0] = lockMessage.getDiagram();
+		args[1] = lockMessage.getSource().toString();
 		for(int i=0; i<lockMessage.getArgNum();i++)
-			args[i+1] = lockMessage.getArgAt(i);
+			args[i+2] = lockMessage.getArgAt(i);
 		
 		bundle.addPacket(new OSCMessage(
 				OSC_NAME_PREFIX+lockMessage.getName().toString(), 
-				args
-				)
-		);
+				args));
 		try{
 			writeBundle(channel);
 		}catch(IOException u){
-			/* give a more user friendly message */
-			throw new IOException("Could not send data to the server",u);
+			throw new IOException(
+					/* give a more user friendly message */
+					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
+		}
+	}
+	
+	@Override
+	public void send(SocketChannel channel, AwarenessMessage awareMsg) throws IOException {
+		/* OSC message args = [diagram name, siagram event action source]*/
+		bundle = new CCmIOSCBundle(awareMsg.getTimestamp());
+		Object[] args = new Object[2];
+		args[0] = awareMsg.getDiagram();
+		args[1] = awareMsg.getSource().toString();
+		
+		bundle.addPacket(new OSCMessage(
+				OSC_NAME_PREFIX+awareMsg.getName().toString(),
+				args));
+		try{
+			writeBundle(channel);
+		}catch(IOException u){
+			throw new IOException(
+					/* give a more user friendly message */
+					ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
 		}
 	}
 
-	@Override
-	public Command receiveCommand(SocketChannel channel) throws IOException {
-		OSCBundle bundle = readBundle(channel);
-		OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
-		String name = oscMessage.getName();
-		assert(name.startsWith(""+OSC_NAME_PREFIX));
-		name = name.substring(1); // chop off the trailing '/'
-		int offset = CMD_DIAGRAM_INDEX+1;
-		Object args[] = new Object[oscMessage.getArgCount()-1];			 
-		for(int i=0; i< args.length;i++)
-			args[i] = oscMessage.getArg(i+offset);
-		return new Command(
-				Command.valueOf(name),
-				(String)oscMessage.getArg(CMD_DIAGRAM_INDEX),
-				args,
-				bundle.getTimeTag()
-		);
-	}
+//	@Override
+//	public Command receiveCommand(SocketChannel channel) throws IOException {
+//		OSCBundle bundle = readBundle(channel);
+//		OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
+//		String name = oscMessage.getName();
+//		assert(name.startsWith(""+OSC_NAME_PREFIX));
+//		name = name.substring(1); // chop off the trailing '/'
+//		Object args[] = new Object[oscMessage.getArgCount()-CMD_OFFSET];			 
+//		for(int i=0; i< args.length;i++)
+//			args[i] = oscMessage.getArg(i+CMD_OFFSET);
+//		return new Command(
+//				Command.valueOf(name),
+//				(String)oscMessage.getArg(CMD_DIAGRAM_INDEX),
+//				args,
+//				bundle.getTimeTag(),
+//				DiagramEventSource.valueOf((String)oscMessage.getArg(CMD_SOURCE_INDEX))
+//		);
+//	}
 	
 	@Override
 	public Reply receiveReply(SocketChannel channel) throws IOException {
@@ -126,7 +153,8 @@
 				Reply.valueOf(name),
 				(String)oscMessage.getArg(REPLY_DIAGRAM_INDEX),
 				(String)oscMessage.getArg(REPLY_MESSAGE_INDEX),
-				bundle.getTimeTag());
+				bundle.getTimeTag(),
+				DiagramEventSource.valueOf((String)oscMessage.getArg(REPLY_SOURCE_INDEX)));
 	}
 	
 	@Override
@@ -135,15 +163,15 @@
 		OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
 		String name = oscMessage.getName();
 		name = name.substring(1); // chop off the trailing '/'
-		Object args[] = new Object[oscMessage.getArgCount()-1];			 
-		int offset = LOCK_DIAGRAM_INDEX + 1;
+		Object args[] = new Object[oscMessage.getArgCount()-LOCK_OFFSET];			 
 		for(int i=0; i< args.length;i++)
-			args[i] = oscMessage.getArg(i+offset);
+			args[i] = oscMessage.getArg(i+LOCK_OFFSET);
 		return new LockMessage(
 				LockMessage.valueOf(name),
 				bundle.getTimeTag(),
 				(String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
-				args
+				args,
+				DiagramEventActionSource.valueOf((String)oscMessage.getArg(LOCK_SOURCE_INDEX))
 				);
 	}
 	
@@ -153,36 +181,50 @@
 		String name = oscMessage.getName();
 		assert(name.startsWith(""+OSC_NAME_PREFIX));
 		name = name.substring(1); // chop off the trailing '/'
-		if(name.endsWith(Reply.REPLY_NAME_POSTFIX)){ // it's a reply 
+		if(name.endsWith(Reply.NAME_POSTFIX)){ // it's a reply 
 			@SuppressWarnings("unused")
 			Integer len = (Integer)oscMessage.getArg(REPLY_LEN_INDEX); // of no use at the moment
 			Reply reply = new Reply(
 					Reply.valueOf(name),
 					(String)oscMessage.getArg(REPLY_DIAGRAM_INDEX),
 					(String)oscMessage.getArg(REPLY_MESSAGE_INDEX),
-					bundle.getTimeTag());
+					bundle.getTimeTag(),
+					DiagramEventSource.valueOf((String)oscMessage.getArg(REPLY_SOURCE_INDEX)));
 			return reply;
-		}else if (name.endsWith(LockMessage.LOCK_NAME_POSTFIX)){
-			Object args[] = new Object[oscMessage.getArgCount()-1];			 
-			int offset = LOCK_DIAGRAM_INDEX + 1;
+		}else if (name.endsWith(LockMessage.NAME_POSTFIX)){
+			Object args[] = new Object[oscMessage.getArgCount()-LOCK_OFFSET];			 
 			for(int i=0; i< args.length;i++)
-				args[i] = oscMessage.getArg(i+offset);
+				args[i] = oscMessage.getArg(i+LOCK_OFFSET);
 			return new LockMessage(
 					LockMessage.valueOf(name),
 					bundle.getTimeTag(),
 					(String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
-					args
+					args,
+					DiagramEventActionSource.valueOf((String)oscMessage.getArg(LOCK_SOURCE_INDEX)) 
 					);
+		}else if(name.endsWith(AwarenessMessage.NAME_POSTFIX)){ // it's an awareness message 
+			AwarenessMessage.Name awName = AwarenessMessage.valueOf(name);
+			if(awName == AwarenessMessage.Name.USERNAME_A || awName == AwarenessMessage.Name.ERROR_A){
+				return new AwarenessMessage(awName,
+						(String)oscMessage.getArg(AWAR_DIAGRAM_INDEX),
+						(String)oscMessage.getArg(AWAR_SOURCE_INDEX)
+					);
+			}else {
+				return new AwarenessMessage(awName,
+						(String)oscMessage.getArg(AWAR_DIAGRAM_INDEX),
+						DiagramEventActionSource.valueOf((String)oscMessage.getArg(AWAR_SOURCE_INDEX))
+					);
+			}
 		}else{ // it's a command
-			Object args[] = new Object[oscMessage.getArgCount()-1];			 
-			int offset = CMD_DIAGRAM_INDEX + 1;
+			Object args[] = new Object[oscMessage.getArgCount()-CMD_OFFSET];
 			for(int i=0; i< args.length;i++)
-				args[i] = oscMessage.getArg(i+offset);
+				args[i] = oscMessage.getArg(i+CMD_OFFSET);
 			return new Command(
 					Command.valueOf(name),
 					(String)oscMessage.getArg(CMD_DIAGRAM_INDEX),
 					args,
-					bundle.getTimeTag()
+					bundle.getTimeTag(),
+					DiagramEventSource.valueOf((String)oscMessage.getArg(CMD_SOURCE_INDEX))
 			);
 		}
 	}
@@ -192,7 +234,7 @@
 		buffer.rewind().limit(4);
 		while( buffer.hasRemaining() )
 			if( channel.read( buffer ) == -1 )
-				throw new SocketException("Connection closed by peer");
+				throw new SocketException(ResourceBundle.getBundle(Server.class.getName()).getString("error.connection_close"));
 			
 		buffer.rewind();
 		int packetSize = buffer.getInt();
@@ -206,7 +248,7 @@
 		/* read the packet, it must be a bundle containing only one message */
 		while( b.hasRemaining() )
 			if( channel.read( b ) == -1 ) 
-				throw new SocketException("Connection closed by peer");
+				throw new SocketException(ResourceBundle.getBundle(Server.class.getName()).getString("error.connection_close"));
 		b.rewind();
 		return (OSCBundle)codec.decode(b);
 	}
@@ -236,8 +278,15 @@
 	private static final int REPLY_LEN_INDEX = 0;
 	/* position of the diagram Name in the OSC message */
 	private static final int REPLY_DIAGRAM_INDEX = 1;
+	private static final int REPLY_SOURCE_INDEX = 3;
 	private static final int CMD_DIAGRAM_INDEX = 0;
+	private static final int CMD_SOURCE_INDEX = 1;
+	private static final int CMD_OFFSET = 2;
 	private static final int LOCK_DIAGRAM_INDEX = 0;
+	private static final int LOCK_SOURCE_INDEX = 1;
+	private static final int LOCK_OFFSET = 2;
+	private static final int AWAR_DIAGRAM_INDEX = 0;
+	private static final int AWAR_SOURCE_INDEX = 1;
 	/* ------------------------------------------------*/
 	private static final int REPLY_MESSAGE_INDEX = 2;
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Protocol.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Protocol.java	Wed Apr 25 17:09:09 2012 +0100
@@ -34,10 +34,10 @@
 	
 	void send(SocketChannel channel, LockMessage lock) throws IOException;
 	
+	void send(SocketChannel channel, AwarenessMessage awareMsg) throws IOException;
+	
 	Reply receiveReply(SocketChannel channel) throws IOException;
 	
-	Command receiveCommand(SocketChannel channel) throws IOException;
-	
 	LockMessage receiveLockMessage(SocketChannel channel) throws IOException;
 	
 	Message receiveMessage(SocketChannel channel) throws IOException;
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Reply.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Reply.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,6 +19,7 @@
 
 package uk.ac.qmul.eecs.ccmi.network;
 
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 
 /**
@@ -31,14 +32,14 @@
  */
 public class Reply extends Message {
 	
-	public Reply(Name name, String diagram, String message, long timestamp){
-		super(timestamp,diagram);
+	public Reply(Name name, String diagram, String message, long timestamp, DiagramEventSource source){
+		super(timestamp,diagram, source);
 		this.name = name;
 		this.message = message;
 	}
 	
-	public Reply(Name name, String diagram, String message){
-		super(diagram);
+	public Reply(Name name, String diagram, String message, DiagramEventSource source){
+		super(diagram,source);
 		this.name = name;
 		this.message = message;
 	}
@@ -51,6 +52,11 @@
 		return message;
 	}
 	
+	@Override
+	public DiagramEventSource getSource(){
+		return (DiagramEventSource)super.getSource();
+	}
+	
 	/**
 	 * @return the length of the String message conveyed by this Reply 
 	 */
@@ -70,7 +76,7 @@
 		try {
 			name = Name.valueOf(n);
 		}catch(IllegalArgumentException iae){
-			name.setOrigin(n); 
+			iae.printStackTrace();
 		}
 		return name;
 	}
@@ -87,7 +93,7 @@
 	private Name name;
 	private String message;
 	private long timestamp;
-	public static final String REPLY_NAME_POSTFIX = "_R";
+	public static final String NAME_POSTFIX = "_R";
 	public static enum Name implements Message.MessageName {
 		NONE_R,
 		OK_R,
@@ -114,22 +120,5 @@
 		BEND_R,
 		STOP_EDGE_MOVE_R,
 		STOP_NODE_MOVE_R;
-		
-		private Name(){
-			origin = null;
-		}
-		
-		private void setOrigin(String origin){
-			this.origin = origin;
-		}
-		
-		@Override
-		public String toString(){
-			if(origin == null)
-				return super.toString();
-			else return origin;
-		}
-		
-		private String origin;
 	};
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Server.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Server.java	Wed Apr 25 17:09:09 2012 +0100
@@ -34,13 +34,11 @@
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.logging.Handler;
 import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
-import javax.swing.JFrame;
-
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
 import uk.ac.qmul.eecs.ccmi.gui.Diagram;
-import uk.ac.qmul.eecs.ccmi.gui.SpeechLogDialog;
 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
 
@@ -57,8 +55,14 @@
  * and wait for an acknowledging reply. 
  *
  */
-public class Server extends Thread {
+public class Server extends NetworkThread {
 	
+	/**
+	 * Create a new server instance. If another instance is already running then it's shut
+	 * down before the new instance is created. 
+	 * 
+	 * @return a {@Server} instance.
+	 */
 	public static Server createServer(){
 		if(server != null)
 			server.shutdown(server.resources.getString("log.restart"));
@@ -66,6 +70,10 @@
 		return server;
 	}
 	
+	public static Server getServer(){
+		return server;
+	}
+	
 	private Server(){
 		super("Server Thread");
 		mustSayGoodbye = false;
@@ -73,19 +81,32 @@
 		resources = ResourceBundle.getBundle(this.getClass().getName());
 	}
 		
-	public void init(JFrame frame) throws IOException {
+	public void init() throws IOException {
 		if(initialized)
 			return;
 		serverChannel = ServerSocketChannel.open();
 		selector = Selector.open();
 		diagrams = new HashMap<String,Diagram>();
-		scManager = new ServerConnectionManager(diagrams);
+		scManager = new ServerConnectionManager(diagrams,getAwarenessPanelEditor());
 		String portAsString = PreferencesService.getInstance().get("server.local_port",Server.DEFAULT_LOCAL_PORT);
 		int port = Integer.parseInt(portAsString);
 		serverChannel.socket().bind(new InetSocketAddress(port));
 		serverChannel.configureBlocking(false);
 		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
-		logger.addHandler(SpeechLogDialog.getSpeechLogDialog(frame));
+		Handler serverLogHandler = new Handler(){
+			@Override
+			public void close() throws SecurityException {}
+
+			@Override
+			public void flush() {}
+
+			@Override
+			public void publish(LogRecord record) {
+				NarratorFactory.getInstance().speakWholeText(record.getMessage());
+			}
+		};
+		serverLogHandler.setLevel(Level.CONFIG);
+		logger.addHandler(serverLogHandler);
 		logger.config("Server initialized, will listen on port: "+port);
 		initialized = true;
 	}
@@ -109,6 +130,9 @@
 						SocketChannel clientChannel = serverChannel.accept();
 						clientChannel.configureBlocking(false);
 						clientChannel.register(selector, SelectionKey.OP_READ);
+						/* log connection only if it's not from the local channel */
+						if(!"/127.0.0.1".equals(clientChannel.socket().getInetAddress().toString()))
+							logger.info(resources.getString("log.client_connected"));
 					}
 					if(key.isReadable()){
 						try{
@@ -118,7 +142,7 @@
 							SocketChannel channel = (SocketChannel)key.channel(); 
 							channel.close();
 							scManager.removeChannel(channel);
-							logger.severe(ioe.getLocalizedMessage());
+							logger.info(ioe.getLocalizedMessage());
 						}
 					}
 				}
@@ -194,7 +218,7 @@
 	private boolean running;	
 	
 	static {
-		logger = Logger.getLogger(Server.class.getCanonicalName());
+		logger = Logger.getLogger(Server.class.getName());
 		logger.setUseParentHandlers(false);
 		logger.setLevel(Level.CONFIG);
 	}
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Server.properties	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Server.properties	Wed Apr 25 17:09:09 2012 +0100
@@ -2,8 +2,14 @@
 log.start=Server started...
 log.restart=Restarting server...
 log.shutdown=Server shutdown:
+log.client_connected=Peer connected to local server
 exception_msg.not_run=Server not running
 exception_msg.already_shared=Diagram already shared
 
 dialog.error.connection=A problem with the server occurred. {0} is no longer shared with other peers.
-dialog.error.connections=A network problem occurred. All shared diagrams are no longer as such
\ No newline at end of file
+dialog.error.connections=A network problem occurred. All diagrams are no longer shared
+dialog.error.no_send=Could not send data to the server
+
+awareness.msg.user_already_exists=Selected User Name already in use
+
+error.connection_close=Connection with peer closed
\ No newline at end of file
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java	Wed Apr 25 17:09:09 2012 +0100
@@ -25,64 +25,94 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.ResourceBundle;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Logger;
 
 import javax.swing.SwingUtilities;
 
-import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.gui.Edge;
 import uk.ac.qmul.eecs.ccmi.gui.Finder;
 import uk.ac.qmul.eecs.ccmi.gui.Lock;
 import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.AwarenessPanelEditor;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.BroadcastFilter;
+import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter;
 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
-
-
+import uk.ac.qmul.eecs.ccmi.speech.Narrator;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
 
 /* This class manages the different sessions with the clients. whereas the 
  * Server class just listens for connections, this class manages all what's going on about the
  * diagram editing: e.g. command processing consistency check, client updates etc.
  */
 class ServerConnectionManager  {
-	ServerConnectionManager( Map<String,Diagram> diagrams){
+	ServerConnectionManager( Map<String,Diagram> diagrams, AwarenessPanelEditor panelEditor) {
 		this.diagrams = diagrams;
-		diagramChannelAllocations = new HashMap<Diagram,Set<SocketChannel>>();
+		awarenessPanelEditor = panelEditor;
+		diagramChannelAllocations = new HashMap<Diagram,Set<UserNameSocketChannel>>();
 		localhostDiagramElementQueue = new ConcurrentHashMap<String,Queue<DiagramElement>>();
 		protocol = ProtocolFactory.newInstance();
 		lockManager = new ServerLockManager();
+		broadcastFilter = BroadcastFilter.getInstance();
 	}
 	
 	/**
-	 * remove the channel and the locks related to it from the inner data structures 
+	 * Removes the channel and the locks related to it from the inner data structures.
+	 * 
 	 * @param channel the channel to remove
 	 */
-	public void removeChannel(SocketChannel channel){
+	void removeChannel(SocketChannel channel) throws IOException{
 		String diagramName = null;
-		for(Map.Entry<Diagram,Set<SocketChannel>> entry : diagramChannelAllocations.entrySet()){
-			if(entry.getValue().remove(channel)){
+		/* looks for the Set containing this channel */
+		for(Map.Entry<Diagram,Set<UserNameSocketChannel>> entry : diagramChannelAllocations.entrySet()){
+			UserNameSocketChannel unsc = null;
+			for(UserNameSocketChannel userNameSocketChannel : entry.getValue())
+				if(userNameSocketChannel.channel.equals(channel)){
+					unsc = userNameSocketChannel;
+					break;
+				}
+			/* remove the channel from this set of channels */
+			if(entry.getValue().remove(unsc) && !unsc.userName.isEmpty()){
 				diagramName = entry.getKey().getName();
+				awarenessPanelEditor.removeUserName(diagramName, unsc.userName);
+				/* notify the other clients the user has disconnected */
+				AwarenessMessage awMsg = new AwarenessMessage(
+						AwarenessMessage.Name.USERNAME_A,
+						diagramName,
+						""+AwarenessMessage.USERNAMES_SEPARATOR+unsc.userName
+						);
+				for(UserNameSocketChannel userNameSocketChannel : entry.getValue()){
+					protocol.send(userNameSocketChannel.channel, awMsg);
+				}
+				break;
 			}
+			
 		}
+		/* all locks held by disconnected user are released */
 		lockManager.removeLocks(channel, diagramName);
 	}
 	
-	public void handleMessage(SocketChannel channel) throws IOException{
+	void handleMessage(SocketChannel channel) throws IOException{
 		Message message = protocol.receiveMessage(channel);
 		if(message instanceof Command){
 			handleCommand((Command)message, channel);
-		}else {
+		}else if(message instanceof LockMessage) {
 			handleLockMessage((LockMessage)message,channel);
+		}else { // awareness message - broadcast the message 
+			handleAwarenessMessage((AwarenessMessage)message,channel,null);
 		}
 	}
 	
@@ -91,16 +121,30 @@
 		String name = lockMessage.getName().toString();
 		String diagramName = lockMessage.getDiagram(); 
 		
+		Diagram diagram = null;
+		synchronized(diagrams){
+			diagram = diagrams.get(diagramName);
+		}
+		if(diagram == null){
+			replyLockMessage(channel,diagramName,false);
+			return;
+		}
+		
 		/* spot the tree node the message refers to */		
 		int[] path = new int[lockMessage.getArgNum()];
 		for(int i = 0; i< path.length;i++){
 			path[i] = (Integer)lockMessage.getArgAt(i);
 		}
-		synchronized(diagram.getCollectionModel().getMonitor()){
-			treeNode = Finder.findTreeNode(path, (DiagramModelTreeNode)diagram.getTreeModel().getRoot());
-		}
+		
+		DiagramTreeNode treeNode = null;
+		/* synchronize with the event dispatching thread */
+		ReentrantLock monitor = diagram.getCollectionModel().getMonitor(); 
+		monitor.lock();
+
+		treeNode = Finder.findTreeNode(path, (DiagramTreeNode)diagram.getTreeModel().getRoot());
 		/* the tree node has been deleted, lock cannot be granted */
 		if(treeNode == null){
+			monitor.unlock();
 			replyLockMessage(channel,diagramName,false);
 			return;
 		}
@@ -108,61 +152,139 @@
 		
 		/* check whether it's a GET or YIELD message and act accordingly */
 		if(name.startsWith(LockMessage.GET_LOCK_PREFIX)){
-			boolean succeeded = lockManager.requestLock(treeNode, lock, channel, diagramName);
-			replyLockMessage(channel,diagramName,succeeded);	
+			// System.out.println("get lock source:"+ lockMessage.getSource());
+			boolean succeeded;
+			succeeded = lockManager.requestLock(treeNode, lock, channel, diagramName);
+			monitor.unlock();
+			/* send the response */
+			replyLockMessage(channel,diagramName,succeeded);
+			if(succeeded && broadcastFilter.accept(lockMessage.getSource())){
+				DiagramEventActionSource processedSource = broadcastFilter.process(lockMessage.getSource()); // changes according to configuration; 
+				
+				//select node is a temporary record, therefore it doesn't need to be stored 
+				if(processedSource.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION){
+					Set<UserNameSocketChannel> userNames = diagramChannelAllocations.get(diagram);
+					/* saves the diagramEventActionSource for when the lock is yielded */
+					if(userNames != null){
+						for(UserNameSocketChannel userName : userNames){
+							if(userName.channel.equals(channel)){
+								userName.lockAwarenessSources.add(processedSource);
+							}
+						}
+					}
+				}
+				/* handle the awareness message piggybacked in the lock message */
+				AwarenessMessage awarMsg = new AwarenessMessage(
+						AwarenessMessage.Name.START_A,
+						diagramName,
+						processedSource
+						);
+				handleAwarenessMessage(awarMsg,channel,diagram);
+			}
 		}else{ // yield lock 
-			lockManager.releaseLock(treeNode, lock, channel,diagramName);
+			boolean released = lockManager.releaseLock(treeNode, lock, channel,diagramName);
+			monitor.unlock();
+			DiagramEventActionSource source = lockMessage.getSource(); 
+			/* it's NULL for NOTES lock and SELECT_NODE_FOR_EDGE_CREATION must not clean the text panel, because its record is temporary */ 
+			if(released && source != DiagramEventActionSource.NULL && source.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION){ 
+				
+				if(source.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION){
+					/* unselect node for edge creation is treated differently because it doesn't * 
+					 * clear the text panel but adds another record, which is temporary          */
+					handleAwarenessMessage(new AwarenessMessage(
+							AwarenessMessage.Name.STOP_A,
+							diagramName,
+							source),
+						channel,
+						diagram);
+					return;
+				}
+				
+				/* retrieves the diagramEventActionSource: when the lock was gotten, the source was stored in   *
+				 * userName.lockAwarenessSource. This is done because the broadcast filter configuration might  *
+				 * have changed in the meanwhile but we still need to send the aw msg with the original source  *
+				 * or the clients won't be able to pick out the string to delete                                */
+				DiagramEventActionSource savedSource = removeEventActionSource(channel,source.getSaveID(),diagram);
+
+				/* saved source = null means the broadcast filter didn't let the get_lock message 
+				 * this yield_lock message is referring to. Move on */
+				if(savedSource == null){
+					return;
+				}
+				
+				AwarenessMessage awMsg = new AwarenessMessage(
+						AwarenessMessage.Name.STOP_A,
+						diagramName,
+						savedSource
+						);
+				handleAwarenessMessage(awMsg,channel,diagram);
+			}
 		}
 	}
 	
 	private void handleCommand(final Command cmd, SocketChannel channel) throws IOException{	
 		/* init some variables we're gonna use in (nearly) every branch of the switch */
 		final String diagramName = cmd.getDiagram();
-		synchronized(diagrams){
-			diagram = diagrams.get(diagramName);
+		Diagram diagram = null;
+		
+		if(cmd.getName() != Command.Name.LIST){
+			synchronized(diagrams){
+				diagram = diagrams.get(diagramName);
+			}
+			if(diagram == null)
+				protocol.send(channel, new Reply(Reply.Name.ERROR_R,diagramName,"Diagram "+diagramName+" does not exists",cmd.getSource()));
 		}
-		node = null;
-		edge = null;
+		Node node = null;
+		Edge edge = null;
 		boolean broadcast = true;
-
+		
+		DiagramEventSource source = cmd.getSource();
+		if(channel == localChannel)
+			source = source.getLocalSource();
+		/* set the diagram id so the haptic will update the diagram specified by the command and not the active tab's */
+		if(diagram != null)
+			source.setDiagramName(diagram.getName());
 		/* log the command through the interaction logger, if any */
 		Command.log(cmd,(channel == localChannel) ? "local command received" : "remote command received");
 		//System.out.println("ServerConnectionManager: received command "+cmd.getName()); 
 		switch(cmd.getName()){
-		case LOCAL : 	
+		case LOCAL : // the local socket makes itself known to the server
 			localChannel = channel;
-			Set<SocketChannel> list = new HashSet<SocketChannel>();
-			list.add(localChannel);
+			Set<UserNameSocketChannel> list = new HashSet<UserNameSocketChannel>();
+			list.add(new UserNameSocketChannel(localChannel,AwarenessMessage.getDefaultUserName()));
 			diagramChannelAllocations.put(diagram, list);
 			broadcast = false;
 			break;
-		case LIST :
+		case LIST : // ask for the list of available diagrams on the server
 			StringBuilder names = new StringBuilder(""); 
 			synchronized(diagrams){
 				for(String s : diagrams.keySet()){
 					names.append(s).append('\n');
 				}
 			}
-			protocol.send(channel, new Reply(Reply.Name.LIST_R,"",names.toString()));
+			protocol.send(channel, new Reply(Reply.Name.LIST_R,"",names.toString(),DiagramEventSource.NONE));
 			broadcast = false;
 			break;
-		case GET :
+		case GET : // ask for a diagram xml
 			try{
-				if(diagram == null){
-					protocol.send(channel, new Reply(Reply.Name.ERROR_R,diagramName,"Diagram "+diagramName+" does not exists"));
-					break;
-				}
+				diagram.getCollectionModel().getMonitor().lock();
+				Set<UserNameSocketChannel> userNames = diagramChannelAllocations.get(diagram); 
 				ByteArrayOutputStream out = new ByteArrayOutputStream();
 				PersistenceManager.encodeDiagramInstance(diagram, new BufferedOutputStream(out));
+				diagram.getCollectionModel().getMonitor().unlock();
 				try{
-					protocol.send(channel, new Reply(Reply.Name.GET_R,diagramName,out.toString("UTF-8")));
+					protocol.send(channel, new Reply(Reply.Name.GET_R,diagramName,out.toString("UTF-8"),DiagramEventSource.NONE));
+					for(UserNameSocketChannel sc: userNames){
+						protocol.send(channel, new AwarenessMessage(AwarenessMessage.Name.USERNAME_A,diagramName,sc.userName));
+					}
 				}catch(IOException ioe){
 					throw ioe;
 				}
-				diagramChannelAllocations.get(diagram).add(channel);
+				userNames.add(new UserNameSocketChannel(channel));
 				broadcast = false;
 			}catch(Exception e){
-				// log and discard the packet
+				// close the socket, log and discard the packet
+				try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
 				Server.logger.severe(e.getMessage());
 			}
 			break;
@@ -174,71 +296,142 @@
 			}else{
 				double dx = (Double)cmd.getArgAt(1);
 				double dy = (Double)cmd.getArgAt(2);
-				synchronized(diagram.getCollectionModel().getMonitor()){
-					node = Finder.findNode((String)cmd.getArgAt(0),diagram.getNodePrototypes());
-					node = (Node)node.clone();
-					/* Place the top left corner of the bounds at the origin. It might be different from     *
-            		 * the origin, as it depends on how the clonation is implemented internally. These calls * 
-            		 * to translate are not notified to any listener as the node is not in the model yet     */
-            		Rectangle2D bounds = node.getBounds();
-            		node.translate(new Point2D.Double(),-bounds.getX(),-bounds.getY());
-					/* perform the actual translation from the origin */
-            		node.translate(new Point2D.Double(), dx, dy);
+				node = Finder.findNode((String)cmd.getArgAt(0),diagram.getNodePrototypes());
+				node = (Node)node.clone();
+				/* Place the top left corner of the bounds at the origin. It might be different from     *
+        		 * the origin, as it depends on how the clonation is implemented internally. These calls * 
+        		 * to translate are not notified to any listener as the node is not in the model yet     */
+        		Rectangle2D bounds = node.getBounds();
+        		node.translate(new Point2D.Double(),-bounds.getX(),-bounds.getY(),DiagramEventSource.NONE);
+				/* perform the actual translation from the origin */
+        		node.translate(new Point2D.Double(), dx, dy, DiagramEventSource.NONE);
+			}
+			/* wait for the node to be inserted in the model so that it gets an id, which is then   *
+			 * used in the awareness message to notify other users that the node has been inserted  */
+			try {
+				SwingUtilities.invokeAndWait(new CommandExecutor.Insert(diagram.getCollectionModel(),node,null,source));
+			} catch (Exception exception) {
+				throw new RuntimeException(exception); // must never happen 
+			}
+			/* send the reply to the client which issued the command */
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.INSERT_NODE_R,diagramName,"Insert new Node",source.getLocalSource()));
+			
+			DiagramEventActionSource actionSource = new DiagramEventActionSource(source,
+					Command.Name.INSERT_NODE,node.getId(),node.getName()); 
+			if(broadcastFilter.accept(actionSource)){
+				/* must set the username to the one of the client who sent the command       *
+				 * otherwise the local username is automatically assigned in the constructor */
+				for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
+					if(sc.channel.equals(channel))
+						actionSource.setUserName(sc.userName);
+				}
+				/* process on the broadcast filter */
+				DiagramEventActionSource processedSource = broadcastFilter.process(actionSource);
+				/* since no lock is used we must send an awareness message without piggybacking */
+				AwarenessMessage awMsg = new AwarenessMessage(
+						AwarenessMessage.Name.START_A,
+						diagramName,
+						processedSource
+						); 
+				if(channel != localChannel){
+					/* update the local awareness panel and speech */ 
+					awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), DisplayFilter.getInstance().processForText(processedSource));
+					NarratorFactory.getInstance().speakWholeText(DisplayFilter.getInstance().processForSpeech(processedSource), Narrator.SECOND_VOICE);
+				}
+				/* broadcast the awareness message to all the clients but one which sent the    *
+				 * command and the local one to inform them the action has started              */
+				for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
+					if(!sc.channel.equals(channel) && !sc.channel.equals(localChannel)){
+						protocol.send(sc.channel, awMsg);
+					}
 				}
 			}
-			SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(),node));
-			/* send the reply to the client which issued the command */
-			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.INSERT_NODE_R,diagramName,"Insert new Node"));
 			break;
 		case REMOVE_NODE :
 			if(channel == localChannel){
 				/* if the command is coming from the local user then there is 		    *
 				 * a diagram element queued, which is the one the user wants  to remove */
 				node = (Node)localhostDiagramElementQueue.get(diagramName).poll();
+				lockManager.removeLocks(node, diagramName);
 			}else{
-				synchronized(diagram.getCollectionModel().getMonitor()){
-					node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-				}
+				diagram.getCollectionModel().getMonitor().lock();
+				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+				lockManager.removeLocks(node, diagramName);
+				diagram.getCollectionModel().getMonitor().unlock();
 			}
+			/* remove the action source, like when a lock is yielded, for this node */
+			removeEventActionSource(channel,node.getId(),diagram);
 			/* wait for the Event Dispatching Thread to delete the edge, in order to avoid collisions  *
 			 * with other locks, e.g. locking again an edge before the EDT deletes it                  */
 			try {
-				lockManager.removeLocks(node, diagramName);
-				SwingUtilities.invokeAndWait(new CommandExecutor.Remove(diagram.getCollectionModel(),node));
+				SwingUtilities.invokeAndWait(new CommandExecutor.Remove(diagram.getCollectionModel(),node,source));
 			} catch (Exception exception) {
 				throw new RuntimeException(exception); // must never happen 
 			}
 			/* send the reply to the client which issued the command */
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.REMOVE_NODE_R,diagramName,"Node with id "+ node.getId() +" removed"));
+				protocol.send(channel, new Reply(Reply.Name.REMOVE_NODE_R,diagramName,"Node with id "+ node.getId() +" removed",source.getLocalSource()));
 			break;
 		case INSERT_EDGE :
+			long[] nodesToConnect = null;
 			if(channel == localChannel){
 				/* if the command is coming from the local user then there is a diagram  *
 				 * element queued, which is the one the user wanted to insert        	 */
 				edge = (Edge)localhostDiagramElementQueue.get(diagramName).poll();
 			}else{
-				synchronized(diagram.getCollectionModel().getMonitor()){
-					edge = Finder.findEdge((String)cmd.getArgAt(0),diagram.getEdgePrototypes());
-					edge = (Edge)edge.clone();
-					List<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(cmd.getArgNum()-1);
-					/* retrieve the nodes to connect by the id, conveyed in the message */
-					for(int i = 1; i<cmd.getArgNum(); i++){
-						Node attachedNode = Finder.findNode((Long)cmd.getArgAt(i),diagram.getCollectionModel().getNodes());
-						nodesToConnect.add(attachedNode);
-					}
-					try {
-						edge.connect(nodesToConnect);
-					} catch (ConnectNodesException e) {
-						throw new RuntimeException();//this must never happen as the check is done by the local client before issuing the command
+				edge = Finder.findEdge((String)cmd.getArgAt(0),diagram.getEdgePrototypes());
+				edge = (Edge)edge.clone();
+				nodesToConnect = new long[cmd.getArgNum()-1];
+				for(int i=0;i<nodesToConnect.length;i++){
+					nodesToConnect[i] = (Long)cmd.getArgAt(i+1);
+				}
+			}
+			/* wait for the edge to be inserted in the model so that it gets an id, which is then   *
+			 * used in the awareness message to notify other users that the node has been inserted  */
+			try {
+				SwingUtilities.invokeAndWait(new CommandExecutor.Insert(diagram.getCollectionModel(), edge, nodesToConnect, source));
+			} catch (Exception exception) {
+				throw new RuntimeException(exception);
+			}
+			/* send the reply to the client which issued the command */
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.INSERT_EDGE_R,diagramName,"Insert new Edge", source.getLocalSource()));
+			
+			/* send the awareness message for edge insertion */
+			DiagramEventActionSource actSource = new DiagramEventActionSource(source,
+					Command.Name.INSERT_EDGE,edge.getId(),edge.getName()); 
+			if(broadcastFilter.accept(actSource)){
+				/* must set the username to the one of the client who sent the command       *
+				 * otherwise the local username is automatically assigned in the constructor */
+				for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
+					if(sc.channel.equals(channel))
+						actSource.setUserName(sc.userName);
+				}
+				
+				/* process it with the broadcast filter */
+				DiagramEventActionSource processedSource = broadcastFilter.process(actSource);
+				
+				/* since no lock is used we must send an awareness message without piggybacking */
+				AwarenessMessage awMsg = new AwarenessMessage(
+						AwarenessMessage.Name.START_A,
+						diagramName,
+						processedSource
+						);
+				
+				if(channel != localChannel){
+					/* update the local awareness panel and speech */ 
+					awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), DisplayFilter.getInstance().processForText(processedSource));
+					NarratorFactory.getInstance().speakWholeText(DisplayFilter.getInstance().processForSpeech(processedSource), Narrator.SECOND_VOICE);
+				}
+				/* broadcast the awareness message to all the clients but one which sent the    *
+				 * command and the local one to inform them the action has started              */
+				for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
+					if(!sc.channel.equals(channel) && !sc.channel.equals(localChannel)){
+						protocol.send(sc.channel, awMsg);
 					}
 				}
 			}
-			SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), edge));
-			/* send the reply to the client which issued the command */
-			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.INSERT_EDGE_R,diagramName,"Insert new Edge"));
 			break;
 		case REMOVE_EDGE :
 			if(channel == localChannel){
@@ -246,178 +439,194 @@
 				/* element queued, which is the one the user wanted to insert            */
 				edge = (Edge)localhostDiagramElementQueue.get(diagramName).poll();
 			}else{
-				synchronized(diagram.getCollectionModel().getMonitor()){
-					edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-				}
+				diagram.getCollectionModel().getMonitor().lock();
+				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+				diagram.getCollectionModel().getMonitor().unlock();
 			}
 			/* wait for the Event Dispatching Thread to delete the edge, in order to avoid collisions  *
 			 * with other locks, e.g. locking again an edge before the EDT deletes it                  */
 			try {
 				lockManager.removeLocks(edge, diagramName);
-				SwingUtilities.invokeAndWait(new CommandExecutor.Remove(diagram.getCollectionModel(), edge));
+				SwingUtilities.invokeAndWait(new CommandExecutor.Remove(diagram.getCollectionModel(), edge, source));
 			} catch (Exception e) {
 				throw new RuntimeException(e); // must never happen
 			}
+			/* remove the action source, like when a lock is yielded, for this node */
+			removeEventActionSource(channel,edge.getId(),diagram);
 			/* send the reply to the client which issued the command */
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.REMOVE_EDGE_R,diagramName,"Edge with id "+ edge.getId() +" removed"));
+				protocol.send(channel, new Reply(Reply.Name.REMOVE_EDGE_R,diagramName,"Edge with id "+ edge.getId() +" removed", source.getLocalSource()));
 			break;
-		case SET_EDGE_NAME :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				de = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-			}
-			SwingUtilities.invokeLater(new CommandExecutor.SetName(de,((String)cmd.getArgAt(1))));
+		case SET_EDGE_NAME : {
+			DiagramElement de = null;
+			diagram.getCollectionModel().getMonitor().lock();
+			de = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			diagram.getCollectionModel().getMonitor().unlock();
+			SwingUtilities.invokeLater(new CommandExecutor.SetName(de,((String)cmd.getArgAt(1)),source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.SET_EDGE_NAME_R,diagramName,"Name set to "+ cmd.getArgAt(1)));
-			break;
-		case SET_NODE_NAME :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				de = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-			}
-			SwingUtilities.invokeLater(new CommandExecutor.SetName(de,((String)cmd.getArgAt(1))));
+				protocol.send(channel, new Reply(Reply.Name.SET_EDGE_NAME_R,diagramName,"Name set to "+ cmd.getArgAt(1),source.getLocalSource()));
+			}break;
+		case SET_NODE_NAME : {
+			DiagramElement de = null;
+			diagram.getCollectionModel().getMonitor().lock();
+			de = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
+			SwingUtilities.invokeLater(new CommandExecutor.SetName(de,((String)cmd.getArgAt(1)),source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.SET_NODE_NAME_R,diagramName,"Name set to "+ cmd.getArgAt(1)));
-			break;	
+				protocol.send(channel, new Reply(Reply.Name.SET_NODE_NAME_R,diagramName,"Name set to "+ cmd.getArgAt(1),source.getLocalSource()));
+			}break;	
 		case SET_PROPERTY :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-			}
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
 			SwingUtilities.invokeLater(new CommandExecutor.SetProperty(
 					node,
 					(String)cmd.getArgAt(1),
 					(Integer)cmd.getArgAt(2),
-					(String)cmd.getArgAt(3)
+					(String)cmd.getArgAt(3),
+					source
 					));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.SET_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(2)+ " set to "+ cmd.getArgAt(3)));
+				protocol.send(channel, new Reply(Reply.Name.SET_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(2)+ " set to "+ cmd.getArgAt(3),source.getLocalSource()));
 			break;
 		case SET_PROPERTIES :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-			}
-			NodeProperties properties = node.getProperties();
-			properties.valueOf((String)cmd.getArgAt(1));
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
+			
 			SwingUtilities.invokeLater(new CommandExecutor.SetProperties(
 					node,
-					properties
+					(String)cmd.getArgAt(1),
+					source
 					));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.SET_PROPERTIES_R,diagramName,"Properties for " + node.getName()+ " set to "+ cmd.getArgAt(1)));
+				protocol.send(channel, new Reply(Reply.Name.SET_PROPERTIES_R,diagramName,"Properties for " + node.getName()+ " set to "+ cmd.getArgAt(1),source.getLocalSource()));
 			break;	
 		case CLEAR_PROPERTIES :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-			}
-			SwingUtilities.invokeLater(new CommandExecutor.ClearProperties(node));
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
+			SwingUtilities.invokeLater(new CommandExecutor.ClearProperties(node,source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.CLEAR_PROPERTIES_R,diagramName,"Propertis of Node "+ node.getName() +" cleared"));
+				protocol.send(channel, new Reply(Reply.Name.CLEAR_PROPERTIES_R,diagramName,"Propertis of Node "+ node.getName() +" cleared",source.getLocalSource()));
 			break;
-		case SET_NOTES :
+		case SET_NOTES :{
+			DiagramTreeNode treeNode = null;
 			int[] path = new int[cmd.getArgNum()-1];
 			for(int i = 0; i< cmd.getArgNum()-1;i++){
 				path[i] = (Integer)cmd.getArgAt(i);
 			}
 			final String notes = (String)cmd.getArgAt(cmd.getArgNum()-1);
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				treeNode = Finder.findTreeNode(path, (DiagramModelTreeNode)diagram.getTreeModel().getRoot());
-			}
-			SwingUtilities.invokeLater(new CommandExecutor.SetNotes(diagram.getTreeModel(),treeNode,notes));
+			diagram.getCollectionModel().getMonitor().lock();
+			treeNode = Finder.findTreeNode(path, (DiagramTreeNode)diagram.getTreeModel().getRoot());
+			diagram.getCollectionModel().getMonitor().unlock();
+			SwingUtilities.invokeLater(new CommandExecutor.SetNotes(diagram.getTreeModel(),treeNode,notes,source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.SET_NOTES_R,diagramName,"Notes for " + treeNode.getName() + " successfully updated"));
-			break;
+				protocol.send(channel, new Reply(Reply.Name.SET_NOTES_R,diagramName,"Notes for " + treeNode.getName() + " successfully updated",source.getLocalSource()));
+			}break;
 		case ADD_PROPERTY :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-			}
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
 			SwingUtilities.invokeLater(new CommandExecutor.AddProperty(
 					node,
 					(String)cmd.getArgAt(1), 
-					(String)cmd.getArgAt(2)
+					(String)cmd.getArgAt(2),
+					source
 					));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.ADD_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " added to "+ cmd.getArgAt(1)));
+				protocol.send(channel, new Reply(Reply.Name.ADD_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " added to "+ cmd.getArgAt(1),source.getLocalSource()));
 			break;
 		case REMOVE_PROPERTY :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-			}
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
 			SwingUtilities.invokeLater(new CommandExecutor.RemoveProperty(
 					node, 
 					(String)cmd.getArgAt(1),
-					(Integer)cmd.getArgAt(2)));
+					(Integer)cmd.getArgAt(2),
+					source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.REMOVE_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " of type "+ cmd.getArgAt(1)+" removed"));
+				protocol.send(channel, new Reply(Reply.Name.REMOVE_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " of type "+ cmd.getArgAt(1)+" removed",source.getLocalSource()));
 			break;
 		case SET_MODIFIERS :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-				indexes = new HashSet<Integer>(cmd.getArgNum()-3);
-				for(int i=3;i<cmd.getArgNum();i++){
-					indexes.add((Integer)cmd.getArgAt(i));
-				}
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			indexes = new HashSet<Integer>(cmd.getArgNum()-3);
+			for(int i=3;i<cmd.getArgNum();i++){
+				indexes.add((Integer)cmd.getArgAt(i));
 			}
+			diagram.getCollectionModel().getMonitor().unlock();
 			SwingUtilities.invokeLater(new CommandExecutor.SetModifiers(
 					node,
 					(String)cmd.getArgAt(1),
 					(Integer)cmd.getArgAt(2),
-					indexes));
+					indexes,
+					source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.SET_MODIFIERS_R,diagramName,"Modifiers for " + cmd.getArgAt(1)+ " successfully set"));
+				protocol.send(channel, new Reply(Reply.Name.SET_MODIFIERS_R,diagramName,"Modifiers for " + cmd.getArgAt(1)+ " successfully set",source.getLocalSource()));
 			break;
 		case SET_ENDLABEL :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-				node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
-			}
-			SwingUtilities.invokeLater(new CommandExecutor.SetEndLabel(edge,node,(String)cmd.getArgAt(2)));
+			diagram.getCollectionModel().getMonitor().lock();
+			edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
+			SwingUtilities.invokeLater(new CommandExecutor.SetEndLabel(edge,node,(String)cmd.getArgAt(2),source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.SET_ENDLABEL_R,diagramName,"Endlabel set to "+ cmd.getArgAt(2) +" for edge "+edge.getName()));
+				protocol.send(channel, new Reply(Reply.Name.SET_ENDLABEL_R,diagramName,"Endlabel set to "+ cmd.getArgAt(2) +" for edge "+edge.getName(),source.getLocalSource()));
 			break;
 		case SET_ENDDESCRIPTION :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-				node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
-			}		
+			diagram.getCollectionModel().getMonitor().lock();
+			edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
 			SwingUtilities.invokeLater(new CommandExecutor.SetEndDescription(
 					edge,
 					node, 
-					(Integer)cmd.getArgAt(2)
+					(Integer)cmd.getArgAt(2),
+					source
 					));
 			if(channel != localChannel)
 				protocol.send(channel, new Reply(Reply.Name.SET_ENDDESCRIPTION_R,diagramName,"End description set to " 
-						+(cmd.getArgNum() == 3 ? cmd.getArgAt(2) : Edge.NO_ARROW_STRING) 
-						+ " for edge "+edge.getName()));
+						+(cmd.getArgNum() == 3 ? cmd.getArgAt(2) : Edge.NO_ENDDESCRIPTION_STRING) 
+						+ " for edge",source.getLocalSource()));
 			break;	
 		case TRANSLATE_NODE :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
-			}
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
 			SwingUtilities.invokeLater(new CommandExecutor.Translate(
 					node,
 					new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
 					(Double)cmd.getArgAt(3),
-					(Double)cmd.getArgAt(4)
+					(Double)cmd.getArgAt(4),
+					source
 					));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.TRANSLATE_NODE_R,diagramName,"Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")"));
+				protocol.send(channel, new Reply(Reply.Name.TRANSLATE_NODE_R,diagramName,"Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")",source.getLocalSource()));
 			break;
 		case TRANSLATE_EDGE :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-			}
+			diagram.getCollectionModel().getMonitor().lock();
+			edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			diagram.getCollectionModel().getMonitor().unlock();
 			SwingUtilities.invokeLater(new CommandExecutor.Translate(
 					edge,
 					new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
 					(Double)cmd.getArgAt(3),
-					(Double)cmd.getArgAt(4)
+					(Double)cmd.getArgAt(4),
+					source
 					));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.TRANSLATE_EDGE_R,diagramName,"Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")"));
+				protocol.send(channel, new Reply(Reply.Name.TRANSLATE_EDGE_R,
+						diagramName,
+						"Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")",
+						source.getLocalSource())
+				);
 			break;
 		case BEND :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-			}
+			diagram.getCollectionModel().getMonitor().lock();
+			edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			diagram.getCollectionModel().getMonitor().unlock();
 			Point2D bendStart = null;
 			if(cmd.getArgNum() == 5){
 				bendStart = new Point2D.Double((Double)cmd.getArgAt(3),(Double)cmd.getArgAt(4));
@@ -425,40 +634,119 @@
 			SwingUtilities.invokeLater(new CommandExecutor.Bend(
 					edge,
 					new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
-					bendStart
+					bendStart,
+					source
 			));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.BEND_R,diagramName,"Bend at point: ("+(Double)cmd.getArgAt(1)+","+(Double)cmd.getArgAt(1)+")"));
+				protocol.send(channel, new Reply(Reply.Name.BEND_R,diagramName,"Bend at point: ("+(Double)cmd.getArgAt(1)+","+(Double)cmd.getArgAt(1)+")",cmd.getSource().getLocalSource()));
 			break;
 		case STOP_EDGE_MOVE : 
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
-			}
-			SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge));
+			diagram.getCollectionModel().getMonitor().lock();
+			edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			diagram.getCollectionModel().getMonitor().unlock();
+			SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge,source));
 			if(channel != localChannel)
-				protocol.send(channel, new Reply(Reply.Name.STOP_EDGE_MOVE_R,diagramName,"Undo straight bends"));
+				protocol.send(channel, new Reply(Reply.Name.STOP_EDGE_MOVE_R,diagramName,"Undo straight bends",source.getLocalSource()));
 			break;	
 		case STOP_NODE_MOVE :
-			synchronized(diagram.getCollectionModel().getMonitor()){
-				node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
-			}
-			SwingUtilities.invokeLater(new CommandExecutor.StopMove(node));
+			diagram.getCollectionModel().getMonitor().lock();
+			node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
+			diagram.getCollectionModel().getMonitor().unlock();
+			SwingUtilities.invokeLater(new CommandExecutor.StopMove(node,source));
 			if(channel != localChannel){
-				protocol.send(channel, new Reply(Reply.Name.STOP_NODE_MOVE_R,diagramName,"Stop node move"));
+				protocol.send(channel, new Reply(Reply.Name.STOP_NODE_MOVE_R,diagramName,"Stop node move",source.getLocalSource()));
 			}
 			break;
 		default : throw new RuntimeException(cmd.getName().toString()+ " command not recognized");
 		}
 		if(broadcast){
 			/* broadcast the command to all the clients but the local (uses the same model) and the one which issued the command (got a reply already)*/
-			for(SocketChannel sc : diagramChannelAllocations.get(diagram)){
-				if(sc != localChannel && !sc.equals(channel))
-					protocol.send(sc, cmd);
+			for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
+				if(sc.channel != localChannel && !sc.channel.equals(channel)){
+					protocol.send(sc.channel, cmd);
+				}	
 			}
 		}
 	}
 	
-	public Map<String, Queue<DiagramElement>> getLocalhostMap() {
+	private void handleAwarenessMessage(AwarenessMessage awMsg,SocketChannel channel, Diagram diagram) throws IOException {
+		if(diagram == null)
+			synchronized(diagrams){
+				diagram = diagrams.get(awMsg.getDiagram());
+			}
+		
+		if(awMsg.getName() == AwarenessMessage.Name.ERROR_A){
+			Logger.getLogger(Server.class.getCanonicalName()).info((String)awMsg.getSource());
+			return;
+		}
+		
+		/* for username aw msg checks whether the chosen name is not already used by another client.        * 
+		 * If not changes the source from "newName", sent by the client, into "newName<SEPARATOR>oldName"   *
+		 * in order to broadcast it to the other client and make them replace the new name with the old one */
+		String oldName = ""; 
+		if(awMsg.getName() == AwarenessMessage.Name.USERNAME_A){
+			String userName = (String)awMsg.getSource();
+			UserNameSocketChannel userNameChannel = null;
+			for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
+				if(sc.channel.equals(channel)){
+					userNameChannel = sc;
+					oldName = userNameChannel.userName;
+					
+				}
+				/* if another user already has the name then prevent from getting it */
+				if(sc.userName.equals(userName)){
+					/* user name already in use, send a reply and return */
+					protocol.send(channel, new AwarenessMessage(
+							AwarenessMessage.Name.ERROR_A,
+							awMsg.getDiagram(),
+							ResourceBundle.getBundle(Server.class.getName()).getString("awareness.msg.user_already_exists")
+							));
+					return;
+				}
+			}
+			userNameChannel.userName = userName;
+			/* set the source of the msg for the clients, which don't hold the channel-username association         * 
+			 * and therefore need a message of the form "newName<SEPARATOR>oldName" in order to do the replacement  */
+			awMsg.setSource((String)awMsg.getSource()+AwarenessMessage.USERNAMES_SEPARATOR+oldName);
+		}
+		
+		/* update the local GUI to make the local user aware of the actions */
+		DisplayFilter filter = DisplayFilter.getInstance();
+		if(channel != localChannel && filter != null){
+			if(awMsg.getName() == AwarenessMessage.Name.USERNAME_A){
+				awarenessPanelEditor.replaceUserName(awMsg.getDiagram(), (String)awMsg.getSource());
+			}else{
+				DiagramEventActionSource processedSource = broadcastFilter.process((DiagramEventActionSource)awMsg.getSource()); // changes according to configuration;
+				if(filter.configurationHasChanged()){
+					for(Diagram d : diagramChannelAllocations.keySet())
+						awarenessPanelEditor.clearRecords(d.getName());
+				}
+				
+				/* select and unselect are announced and written (temporary) on the panel, regardless START_A and STOP_A */
+				if(processedSource.getCmd() == Command.Name.SELECT_NODE_FOR_EDGE_CREATION || processedSource.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION){
+					awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), filter.processForText(processedSource));
+					NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(processedSource), Narrator.SECOND_VOICE);
+				}else if(awMsg.getName() == AwarenessMessage.Name.START_A){					
+					awarenessPanelEditor.addRecord(awMsg.getDiagram(), filter.processForText(processedSource));
+					/* announce the just received awareness message via the second voice */
+					NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(processedSource), Narrator.SECOND_VOICE);
+				}else{ // STOP_A
+					/* selection is a timedRecord, therefore no need to remove the record on STOP_A */
+					if(processedSource.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION && processedSource.getCmd() != Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION)
+						awarenessPanelEditor.removeRecord(awMsg.getDiagram(), filter.processForText(processedSource));
+				}
+			}
+		}
+		
+		/* broadcast the awareness message to all the clients but the local and  *
+		 * one which sent it, to inform them the action has started              */
+		for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
+			if(sc.channel != localChannel && !sc.channel.equals(channel))
+				protocol.send(sc.channel, awMsg);
+		}
+	}
+	
+	Map<String, Queue<DiagramElement>> getLocalhostMap() {
 		return localhostDiagramElementQueue;
 	}
 	
@@ -466,26 +754,60 @@
 		protocol.send(channel, new LockMessage(
 				yes ? LockMessage.Name.YES_L : LockMessage.Name.NO_L,
 				diagramName,
-				-1
+				-1,
+				DiagramEventActionSource.NULL
 		));
 	}
 	
-	private Diagram diagram;
-	private Node node;
-	private Edge edge;
-	private DiagramElement de;
-	private DiagramModelTreeNode treeNode;
+	private DiagramEventActionSource removeEventActionSource(SocketChannel channel, long saveID, Diagram diagram){
+		Set<UserNameSocketChannel> userNames = diagramChannelAllocations.get(diagram);
+		if(userNames != null){
+			for(UserNameSocketChannel userName : userNames){
+				if(userName.channel.equals(channel)){
+					for(DiagramEventActionSource s : userName.lockAwarenessSources){
+						if(s.getSaveID() == saveID){
+							userName.lockAwarenessSources.remove(s);
+							return s;
+						}
+					}
+				}
+			}
+		}
+		return null;
+	}
+	
 	private Set<Integer> indexes;
 	/* the String key is the name of diagram, this collection is shared with the class Server * 
 	 * and it's used to retrieve the diagrams out of commands                                 */
 	private Map<String,Diagram> diagrams;
+	/* unique localChannel for all the digrams */
 	private SocketChannel localChannel;
 	/* this map contains all the channels bound to a diagram, so if a change  	* 
 	 * is made to a diagram all its channels are broadcasted through this map   */
-	private Map<Diagram,Set<SocketChannel>> diagramChannelAllocations;
+	private Map<Diagram,Set<UserNameSocketChannel>> diagramChannelAllocations;
 	/* this map is used to pass the reference to elements created by the local client         *  
 	 * (we don't create a new object as well as we do for the nodes created by remote clients */
 	private Map<String, Queue<DiagramElement>> localhostDiagramElementQueue;
 	private Protocol protocol;
 	private ServerLockManager lockManager;
+	private BroadcastFilter broadcastFilter;
+	private AwarenessPanelEditor awarenessPanelEditor; 
+	
+	/* this class holds for each socketChannel the username associated to it 
+	 * and the last received awareness message source,*/
+	private static class UserNameSocketChannel {
+		UserNameSocketChannel(SocketChannel channel){
+			this(channel,"");
+		}
+		
+		UserNameSocketChannel(SocketChannel channel, String userName){
+			this.channel = channel;
+			this.userName = userName;
+			lockAwarenessSources = new LinkedList<DiagramEventActionSource>();
+		}
+		
+		SocketChannel channel;
+		String userName;
+		List<DiagramEventActionSource> lockAwarenessSources;
+	}
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ServerLockManager.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerLockManager.java	Wed Apr 25 17:09:09 2012 +0100
@@ -30,7 +30,7 @@
 import javax.swing.tree.TreeNode;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
-import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceHolderMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode;
 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode;
@@ -74,7 +74,7 @@
 	}
 	
 	/* check if the specified lock is present in the lock list for the specified tree node */
-	private boolean lockExists(DiagramModelTreeNode treeNode, Lock lock, List<LockEntry> locks, SocketChannel channel){
+	private boolean lockExists(DiagramTreeNode treeNode, Lock lock, List<LockEntry> locks, SocketChannel channel){
 		for(LockEntry lockEntry : locks){
 			if(lockEntry.treeNode.equals(treeNode) && lockEntry.lock == lock && !lockEntry.channel.equals(channel))
 				return true;
@@ -83,7 +83,7 @@
 	}
 	
 	/* check if either specified locks is present in the lock list for the specified tree node */
-	private boolean lockExists(DiagramModelTreeNode treeNode, Lock lock1, Lock lock2, List<LockEntry> locks, SocketChannel channel){
+	private boolean lockExists(DiagramTreeNode treeNode, Lock lock1, Lock lock2, List<LockEntry> locks, SocketChannel channel){
 		for(LockEntry lockEntry : locks){
 			if(lockEntry.treeNode.equals(treeNode) && (lockEntry.lock == lock1 || lockEntry.lock == lock2) && !lockEntry.channel.equals(channel) )
 				return true;
@@ -93,7 +93,7 @@
 	
 	/* Check whether the lock can be granted as it does    */  
 	/* not clash with other locks owned by other clients   */
-	private boolean checkLockDependencies(DiagramModelTreeNode treeNode, Lock lock, SocketChannel channel, List<LockEntry> locks){
+	private boolean checkLockDependencies(DiagramTreeNode treeNode, Lock lock, SocketChannel channel, List<LockEntry> locks){
 		/* bookmarks are not shared, we only check them against delete-lock, as editing a  */ 
 		/* bookmark on a tree node that has just been would lead to an inconsistent state  */
 		if(lock != Lock.BOOKMARK)  
@@ -155,7 +155,7 @@
 			/* all the descendants of the element must be non notes-locked or bookmark-locked */ 
 			for(@SuppressWarnings("rawtypes")
 			Enumeration enumeration = treeNode.breadthFirstEnumeration(); enumeration.hasMoreElements();){
-				if(lockExists((DiagramModelTreeNode)enumeration.nextElement(),Lock.NOTES,Lock.BOOKMARK,locks,channel))  
+				if(lockExists((DiagramTreeNode)enumeration.nextElement(),Lock.NOTES,Lock.BOOKMARK,locks,channel))  
 					return false;
 			}
 			
@@ -216,7 +216,8 @@
 	 *   holding a lock which clashes with this request) 
 	 */
 	public static ServerLockManager singletonLockManager;
-	public boolean requestLock(DiagramModelTreeNode treeNode, Lock lock, SocketChannel channel,String diagramName){
+	public boolean requestLock(DiagramTreeNode treeNode, Lock lock, SocketChannel channel,String diagramName){
+//		System.out.println("lock before request:"+lockStatusDescription(diagramName)+"\n----");
 		List<LockEntry> locks = locksMap.get(diagramName);
 		if(locks == null){
 			/* if no object in the diagram has ever been locked      */
@@ -225,8 +226,8 @@
 			locksMap.put(diagramName,locks);
 			singletonLockManager = this;
 		}
-		/* deleting a node will cause all the attached two-ended edges to be deleted, * 
-		 * therefore we need to lock all those edges too, before                      */
+		/* deleting a node will cause all the attached two-ended edges to    * 
+		 * be deleted, therefore we need to lock all those edges too, before */
 		if(lock == Lock.DELETE && treeNode instanceof Node){
 			Node n = (Node)treeNode;
 			for(int i=0; i<n.getEdgesNum();i++){
@@ -254,8 +255,18 @@
 			}
 			return false;
 		}
-		locks.add(new LockEntry(treeNode,lock,channel));
-		//System.out.println(lockStatusDescription(diagramName));
+		
+		/* adds the lock only if it doesn't already exist */
+		boolean add = true; 
+		for(LockEntry l : locks){
+			if(l.channel.equals(channel) && l.lock.equals(lock) && l.treeNode.equals(treeNode)){
+				add = false;
+				break;
+			}
+		}
+		if(add)
+			locks.add(new LockEntry(treeNode,lock,channel));
+//		System.out.println("lock after request:"+lockStatusDescription(diagramName)+"\n----");
 		return true;
 	}
 	/**
@@ -264,14 +275,18 @@
 	 * @param lock the lock type
 	 * @param channel the channel of the client releasing the lock
 	 * @param diagramName the diagram whose tree node is affected by this call
+	 * 
+	 * @return true if a lock was really yielded as a result of the call
 	 */
-	public void releaseLock(DiagramModelTreeNode treeNode, Lock lock, SocketChannel channel, String diagramName){
+	public boolean releaseLock(DiagramTreeNode treeNode, Lock lock, SocketChannel channel, String diagramName){
 		List<LockEntry> locks = locksMap.get(diagramName);
 		Iterator<LockEntry> iterator = locks.iterator();
+		boolean lockReleased = false;
 		while(iterator.hasNext()){
 			LockEntry entry = iterator.next();
 			if(entry.treeNode.equals(treeNode) && entry.lock == lock && entry.channel == channel){
 				iterator.remove();
+				lockReleased = true;
 				if(lock == Lock.DELETE && treeNode instanceof Node)
 					continue; // we have to check for attached edges which must be unlocked too 
 				else
@@ -287,7 +302,8 @@
 				}
 			}
 		}
-		//System.out.println(lockStatusDescription(diagramName));
+//		System.out.println("lock release:"+lockStatusDescription(diagramName)+"\n----");
+		return lockReleased;
 	}
 	
 	/**
@@ -357,7 +373,7 @@
 	private Map<String,List<LockEntry>> locksMap;
 	
 	private static class LockEntry {
-		public LockEntry(DiagramModelTreeNode treeNode,Lock lock,SocketChannel channel) {
+		public LockEntry(DiagramTreeNode treeNode,Lock lock,SocketChannel channel) {
 			this.channel = channel;
 			this.lock = lock;
 			this.treeNode = treeNode;
@@ -365,6 +381,6 @@
 		
 		public SocketChannel channel;
 		public Lock lock;
-		public DiagramModelTreeNode treeNode;
+		public DiagramTreeNode treeNode;
 	}
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EdgeDrawSupport.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EdgeDrawSupport.java	Wed Apr 25 17:09:09 2012 +0100
@@ -49,7 +49,7 @@
 	 * 
 	 */
 
-	public static void drawString(Graphics2D g2, //FIXME ArrowHead class should be in the same package
+	public static void drawString(Graphics2D g2, 
 			Point2D p, Point2D q, ArrowHead arrow, String s, boolean center){
 		if (s == null || s.length() == 0) return;
 		label.setText("<html>" + s + "</html>");
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeEdge.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeEdge.java	Wed Apr 25 17:09:09 2012 +0100
@@ -35,6 +35,7 @@
 import org.w3c.dom.NodeList;
 
 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
 import uk.ac.qmul.eecs.ccmi.gui.Edge;
 import uk.ac.qmul.eecs.ccmi.gui.GraphElement;
 import uk.ac.qmul.eecs.ccmi.gui.LineStyle;
@@ -219,15 +220,15 @@
 	}
 	
 	@Override
-	 public void setEndDescription(DiagramNode diagramNode, int index){
+	 public void setEndDescription(DiagramNode diagramNode, int index, Object source){
 		Node n = (Node)diagramNode;
 		if(index == NO_END_DESCRIPTION_INDEX){
 			 currentHeads.remove(n);
-			 super.setEndDescription(n, index);
+			 super.setEndDescription(n, index,source);
 		 }else{
 			 ArrowHead h = heads[index];
 			 currentHeads.put(n, h);
-			 super.setEndDescription(n, index);
+			 super.setEndDescription(n, index,source);
 		 }
 	}
 	
@@ -277,26 +278,12 @@
 						break;
 					}
 				}
-				setEndDescription(nodesId.get(id),headDescriptionIndex);
+				setEndDescription(nodesId.get(id),headDescriptionIndex,DiagramEventSource.PERS);
 			}
 		}
 	}
 	
 	@Override
-	public int getStipplePattern(){
-		int result = 0;
-		switch(getStyle()){
-		case Solid : result = 0xFFFF;
-		break;
-		case Dashed : result = 0xF0F0;
-		break;
-		case Dotted : result = 0xAAAA ;
-		break;
-		}
-		return result;
-	}
-	
-	@Override
 	public Object clone(){
 		return new SimpleShapeEdge(getType(), getStyle(), heads, getAvailableEndDescriptions(), getMinAttachedNodes(), getMaxAttachedNodes() );
 	}
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.java	Wed Apr 25 17:09:09 2012 +0100
@@ -67,7 +67,6 @@
 		button.addChangeListener(new javax.swing.event.ChangeListener(){
 			@Override
 			public void stateChanged(ChangeEvent e) {
-				/* keep the button disabled until finished is not true */ 
 				((JButton)e.getSource()).setEnabled(finished);
 			}
 		});
--- a/java/src/uk/ac/qmul/eecs/ccmi/sound/BeadsSound.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/BeadsSound.java	Wed Apr 25 17:09:09 2012 +0100
@@ -159,16 +159,11 @@
 	}
 
 	@Override
-	public void setPlayerListener(PlayerListener listener, SoundEvent type){
+	public void setDefaultPlayerListener(PlayerListener listener, SoundEvent type){
 		playerListeners.put(type, listener);
 	}
 	
 	@Override
-	public void unsetPlayerListener(SoundEvent type){
-		playerListeners.remove(type);
-	}
-	
-	@Override
 	public void setMuted(boolean mute){
 		this.mute = mute;
 	}
--- a/java/src/uk/ac/qmul/eecs/ccmi/sound/Sound.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/Sound.java	Wed Apr 25 17:09:09 2012 +0100
@@ -49,11 +49,9 @@
 
 	public void stopLoop(SoundEvent evt);
 
-	public void setPlayerListener(PlayerListener listener,
+	public void setDefaultPlayerListener(PlayerListener listener,
 			SoundEvent type);
 
-	public void unsetPlayerListener(SoundEvent type);
-
 	public void dispose();
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/BeadsAudioPlayer.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,213 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/ 
+
+package uk.ac.qmul.eecs.ccmi.speech;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.sound.sampled.AudioFileFormat;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+
+import net.beadsproject.beads.core.AudioContext;
+import net.beadsproject.beads.core.Bead;
+import net.beadsproject.beads.data.Sample;
+import net.beadsproject.beads.ugens.Gain;
+import net.beadsproject.beads.ugens.Panner;
+import net.beadsproject.beads.ugens.SamplePlayer;
+
+import com.sun.speech.freetts.audio.AudioPlayer;
+
+public class BeadsAudioPlayer implements AudioPlayer {
+	public BeadsAudioPlayer(){
+		format = new AudioFormat(8000f, 16, 1, true, true);
+		ac = new AudioContext(format);
+		volume = 1.0f;
+		monitor = new Object();
+	}
+	
+	public BeadsAudioPlayer(float vol, float pan){
+		this();
+		volume = vol;
+		this.pan = pan;
+	}
+	
+	
+	@Override
+	public void begin(int size) {
+		buffer = new byte[size];
+		bufferPosition = 0;
+		ac = new AudioContext();
+	}
+
+	@Override
+	public void cancel() {
+		
+	}
+
+	@Override
+	public void close() {
+		ac.stop();
+	}
+
+	@Override
+	public boolean drain() {
+		synchronized(monitor){
+			if(!finished)
+				try {
+					monitor.wait();
+				} catch (InterruptedException e) {
+					throw new RuntimeException(e);
+				}
+			finished = false;	
+		}
+		return false;
+	}
+
+	@Override
+	public boolean end() {
+		ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
+		AudioInputStream audioStream = new AudioInputStream(stream, format, bufferPosition/format.getFrameSize());
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		Sample sample = null;
+		try {
+			AudioSystem.write(audioStream, AudioFileFormat.Type.WAVE,out);
+			sample = new Sample(new ByteArrayInputStream(out.toByteArray()));
+		} catch (IOException e) {
+			e.printStackTrace();
+			return false;
+		} catch (UnsupportedAudioFileException e) {
+			e.printStackTrace();
+			return false;
+		}
+		
+		SamplePlayer player = new SamplePlayer(ac,sample);
+		player.setKillOnEnd(true);
+		Gain g = new Gain(ac,1,volume); 
+		g.addInput(player);
+		final Panner panner = new Panner(ac,pan);
+		panner.addInput(g);
+		player.setKillListener(new Bead(){
+			@Override
+			protected void messageReceived(Bead message){
+				panner.kill();
+				synchronized(monitor){
+					finished = true;
+					monitor.notify();
+				}
+			}
+		});
+		
+		/* starts playing the sample */
+		ac.out.addInput(panner);
+		ac.start();
+		return true;
+	}
+
+	@Override
+	public AudioFormat getAudioFormat() {
+		return format;
+	}
+
+	@Override
+	public long getTime() {
+		return -1L;
+	}
+
+	@Override
+	public float getVolume() {
+		return volume;
+	}
+
+	@Override
+	public void pause() {
+		
+	}
+
+	@Override
+	public void reset() {
+		
+	}
+
+	@Override
+	public void resetTime() {
+		
+	}
+
+	@Override
+	public void resume() {
+		
+	}
+
+	@Override
+	public void setAudioFormat(AudioFormat format) {
+		this.format = format;
+		ac.setInputAudioFormat(format);
+	}
+
+	@Override
+	public void setVolume(float vol) {
+		volume = vol;
+	}
+	
+	public void setPan(float pan){
+		this.pan = pan;
+	}
+	
+	public float getPan(){
+		return pan;
+	}
+
+	@Override
+	public void showMetrics() {
+		
+	}
+
+	@Override
+	public void startFirstSampleTimer() {
+		
+	}
+
+	@Override
+	public boolean write(byte[] audioData) {
+		return write(audioData,0,audioData.length);
+	}
+
+	@Override
+	public boolean write(byte[] audioData, int offset, int size) {
+		System.arraycopy(audioData, offset, buffer, bufferPosition, size);
+		bufferPosition += size;
+		return true;
+	}
+	
+	private byte[] buffer;
+	private int bufferPosition;
+	private AudioFormat format;
+	private float volume;
+	private float pan;
+	private Object monitor;
+	private boolean finished;
+	private AudioContext ac;
+
+}
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/DummyNarrator.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/DummyNarrator.java	Wed Apr 25 17:09:09 2012 +0100
@@ -19,7 +19,7 @@
 
 package uk.ac.qmul.eecs.ccmi.speech;
 
-/*
+/**
 * A dummy implementation of the Narrator interface. All its methods are empty,
 * so every call will have no effect whatsoever.
 */
@@ -29,18 +29,27 @@
 	public void init() throws NarratorException {}
 	
 	@Override
-	public void setMuted(boolean muted) {}
+	public void setMuted(boolean muted, int voice) {}
+	
+	@Override
+	public boolean isMuted(int voice){return false;}
 
 	@Override
 	public void shutUp() {}
 
 	@Override
 	public void speak(String text) {}
+	
+	@Override
+	public void speak(String text, int voice){}
 
 	@Override
 	public void speakWholeText(String text){}
 	
 	@Override
+	public void speakWholeText(String text, int voice){}
+	
+	@Override
 	public void dispose() {}
 
 	@Override
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.java	Wed Apr 25 17:09:09 2012 +0100
@@ -28,7 +28,9 @@
 
 	public void init() throws NarratorException;
 	
-	public void setMuted(boolean muted);
+	public void setMuted(boolean muted, int voice);
+	
+	public boolean isMuted(int voice);
 
 	public void setRate(int rate);
 	
@@ -36,12 +38,19 @@
 		
 	public void shutUp();
 
+	public void speak(String text, int voice);
+	
 	public void speak(String text);
 	
 	public void speakWholeText(String text);
+	
+	public void speakWholeText(String text,int voice);
 
 	public void dispose();
 
 	int MIN_RATE = 0;
 	int MAX_RATE = 20;
+	int FIRST_VOICE = 1;
+	int SECOND_VOICE = 2;
+	
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.properties	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.properties	Wed Apr 25 17:09:09 2012 +0100
@@ -23,6 +23,8 @@
 char.dash=dash
 char.underscore=underscore
 char.space=space
+char.asterisk=asterisk
+char.dollar=dollar
 
 error.no_speech=Could not create the speech synthesizer
 
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java	Wed Apr 25 17:09:09 2012 +0100
@@ -23,23 +23,26 @@
 import java.util.ResourceBundle;
 import java.util.concurrent.LinkedBlockingQueue;
 
-import uk.ac.qmul.eecs.ccmi.utils.NativeLibFileWriter;
+import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter;
 import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
 
+import com.sun.speech.freetts.Voice;
+import com.sun.speech.freetts.VoiceManager;
 /*
  * Implementation of the Narrator interface using the Windows system text to speech
  * synthesizer.
  */
 class NativeNarrator implements Narrator {
-	
 	static {
 		nativeLibraryNotFound = true;
 		if(OsDetector.isWindows()){
 			URL url = NativeNarrator.class.getResource("WinNarrator.dll");
 			if(url != null){
-				NativeLibFileWriter fileWriter = new NativeLibFileWriter(url);
-				fileWriter.writeToDisk("CCmIWinNarrator.dll");
+				ResourceFileWriter fileWriter = new ResourceFileWriter(url);
+				fileWriter.writeToDisk(
+					PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),	
+					"CCmIWinNarrator.dll");
 				String path = fileWriter.getFilePath();
 				if(path != null)
 					try{
@@ -55,6 +58,15 @@
 
 	public NativeNarrator(){
 		resources = ResourceBundle.getBundle(Narrator.class.getName());
+		VoiceManager voiceManager = VoiceManager.getInstance();
+		secondaryVoice = voiceManager.getVoice(VOICE_NAME);
+		if(secondaryVoice == null)
+			System.out.println("Could not create voice for the second speaker");
+		else{
+			secondaryVoice.setAudioPlayer(new BeadsAudioPlayer(1.0f,1.0f));
+			secondaryVoice.setRate(250f);
+			secondaryVoice.allocate();
+		}
 	}
 	
 	@Override
@@ -62,7 +74,8 @@
 		if(nativeLibraryNotFound)
 			throw new NarratorException();
 		
-		muted = false;
+		firstVoiceMuted = false;
+		secondVoiceMuted = false;
 		queue = new LinkedBlockingQueue<QueueEntry>();
 		executor = new Executor();
 		boolean success = _init();
@@ -74,8 +87,19 @@
 	}
 
 	@Override
-	public void setMuted(boolean muted) {
-		this.muted = muted;	
+	public void setMuted(boolean muted, int voice) {
+		if(voice == SECOND_VOICE)
+			secondVoiceMuted = muted;
+		else
+			firstVoiceMuted = muted;
+	}
+	
+	@Override
+	public boolean isMuted(int voice){
+		if(voice == SECOND_VOICE)
+			return secondVoiceMuted;
+		else	
+			return firstVoiceMuted;
 	}
 	
 	@Override
@@ -98,8 +122,8 @@
 	}
 
 	@Override
-	public void speak(String text) {
-		if(muted)
+	public void speak(String text, int voice) {
+		if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE)
 			return;
 		if(" ".equals(text))
 			text = resources.getString("char.space");
@@ -107,14 +131,29 @@
 			text = resources.getString("char.new_line");
 		else if(text.contains(".ccmi"))
 			text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell"));
-		queue.add(new QueueEntry(text,false));
+		queue.add(new QueueEntry(text,false,voice));
+	}
+	
+	public void speak(String text){
+		speak(text,Narrator.FIRST_VOICE);
 	}
 
 	@Override
+	public void speakWholeText(String text, int voice) {
+		if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE)
+			return;
+		if(" ".equals(text))
+			text = resources.getString("char.space");
+		else if("\n".equals(text))
+			text = resources.getString("char.new_line");
+		else if(text.contains(".ccmi"))
+			text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell"));
+		queue.add(new QueueEntry(text,true,voice));
+	}
+	
+	@Override
 	public void speakWholeText(String text) {
-		if(muted)
-			return;
-		queue.add(new QueueEntry(text,true));
+		speakWholeText(text, Narrator.FIRST_VOICE);
 	}
 
 	@Override
@@ -136,12 +175,15 @@
 	
 	private native void _shutUp();
 	
-	private boolean muted;
+	private volatile boolean firstVoiceMuted;
+	private volatile boolean secondVoiceMuted;
 	private int rate;
 	private  LinkedBlockingQueue<QueueEntry> queue;
 	private Executor executor;
 	private ResourceBundle resources;
+	private Voice secondaryVoice;
 	private static String DEFAULT_RATE_VALUE = "13";
+	private static final String VOICE_NAME = "kevin16";
 	private static boolean nativeLibraryNotFound;
 	
 	private class Executor extends Thread{
@@ -161,12 +203,16 @@
 				}
 				if(!entry.speakToEnd && queue.peek() != null)
 					continue;/* the user submitted another text to be spoken out and this can be overwritten */
-				if(entry.speakToEnd){
+				if(entry.speakToEnd && entry.voice == Narrator.FIRST_VOICE){
 					_speakWholeText(entry.text);
-				}else{
+				}else if(entry.voice == Narrator.FIRST_VOICE){
 					_speak(entry.text);
+				}else if(secondaryVoice != null){
+					secondaryVoice.speak(entry.text);
 				}
 			}
+			if(secondaryVoice != null)
+				secondaryVoice.deallocate();
 			_dispose();
 		}
 		
@@ -174,12 +220,14 @@
 	}
 	
 	private static class QueueEntry {
-		QueueEntry(String text, boolean speakToEnd){
+		QueueEntry(String text, boolean speakToEnd, int voice){
 			this.text = text;
 			this.speakToEnd = speakToEnd;
+			this.voice = voice;
 		}
 		String text;
 		boolean speakToEnd;
+		int voice;
 	}
 
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java	Wed Apr 25 17:09:09 2012 +0100
@@ -42,14 +42,17 @@
 import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JSpinner;
+import javax.swing.JTabbedPane;
 import javax.swing.JTextArea;
 import javax.swing.JTextField;
+import javax.swing.JTree;
 import javax.swing.KeyStroke;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.JTextComponent;
 
 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
 
 /**
  * A class providing static utilities methods concerning the text to speech synthesis. 
@@ -79,7 +82,12 @@
 		}else if(c instanceof JSpinner){
 			b.append(' ').append(resources.getString("component.spinner"));
 			b.append(((JSpinner)c).getValue());
-		}else{
+		}else if(c instanceof JTabbedPane){
+			Component comp = ((JTabbedPane)c).getSelectedComponent();
+			if(comp == null)
+				return "";
+			b.append(' ').append( comp.getName());
+		}else if(!(c instanceof JTree)){
 			b.append(' ').append(c.getAccessibleContext().getAccessibleRole());
 		}
 		return b.toString();
@@ -101,6 +109,7 @@
 				Component next = policy.getComponentAfter(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
 				next.requestFocusInWindow();
 				NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(next));
+				InteractionLog.log("TABBED PANE","change focus ",next.getAccessibleContext().getAccessibleName());
 			}
 		});
 		
@@ -114,8 +123,17 @@
 				Component previous = policy.getComponentBefore(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
 				previous.requestFocusInWindow();
 				NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(previous));
+				InteractionLog.log("TABBED PANE","change focus ",previous.getAccessibleContext().getAccessibleName());
 			}
 		});
+		
+		/* shut up the narrator upon pressing ctrl */
+//		component.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown");
+//		component.getActionMap().put("ctrldown",new AbstractAction(){
+//			public void actionPerformed(ActionEvent evt){
+//				NarratorFactory.getInstance().shutUp();
+//			}
+//		});
 	}
 	
 	private static void disableTraversalKey(Container container){
@@ -127,10 +145,19 @@
 		}
 	}
 	
+	public static KeyListener getSpeechKeyListener(boolean editableComponent, boolean secondVoice){
+		if(!editableComponent)
+			return new SpeechKeyListener(false,secondVoice);
+		return speechKeyListener;
+	}
+	
+	/**
+	 * Returns a {@code speechKeyListener} using first voice (default) 
+	 * @param editableComponent
+	 * @return 
+	 */
 	public static KeyListener getSpeechKeyListener(boolean editableComponent){
-		if(!editableComponent)
-			return new SpeechKeyListener(false);
-		return speechKeyListener;
+		return getSpeechKeyListener(editableComponent,false);
 	}
 	
 	public static ItemListener getSpeechComboBoxItemListener(){
@@ -159,9 +186,11 @@
 		boolean isFirstLine;
 		boolean isLastLine;
 		boolean editableComponent;
+		int voice;
 		
-		SpeechKeyListener(boolean editablecomponent){
+		SpeechKeyListener(boolean editablecomponent, boolean useSecondVoice){
 			this.editableComponent = editablecomponent;
+			voice = useSecondVoice ? Narrator.SECOND_VOICE : Narrator.FIRST_VOICE; 
 		}
 		
 		@Override
@@ -169,55 +198,61 @@
 			/* this will manage digit or letter characters */
 			if(!isTab && !evt.isControlDown() && editableComponent){
 				if(Character.isLetterOrDigit(evt.getKeyChar())){
-					NarratorFactory.getInstance().speak(String.valueOf(evt.getKeyChar()));
+					NarratorFactory.getInstance().speak(String.valueOf(evt.getKeyChar()),voice);
 				}else{
 					/* this will manage special characters with a letter representation */
 					switch(evt.getKeyChar()){
 					case '\n' :
 						if(!(evt.getSource() instanceof JTextField))
-							NarratorFactory.getInstance().speak(resources.getString("char.new_line"));
+							NarratorFactory.getInstance().speak(resources.getString("char.new_line"),voice);
 						break;
 					case ' ' :
-						NarratorFactory.getInstance().speak(resources.getString("char.space"));
+						NarratorFactory.getInstance().speak(resources.getString("char.space"),voice);
 						break;	
 					case '@' : 
-						NarratorFactory.getInstance().speak(resources.getString("char.at"));
+						NarratorFactory.getInstance().speak(resources.getString("char.at"),voice);
 						break;
+					case '*' : 
+						NarratorFactory.getInstance().speak(resources.getString("char.asterisk"),voice);
+						break;	
+					case '$' : 
+						NarratorFactory.getInstance().speak(resources.getString("char.dollar"),voice);
+						break;	
 					case '.' :
-						NarratorFactory.getInstance().speak(resources.getString("char.dot"));
+						NarratorFactory.getInstance().speak(resources.getString("char.dot"),voice);
 						break;
 					case ',' :
-						NarratorFactory.getInstance().speak(resources.getString("char.comma"));
+						NarratorFactory.getInstance().speak(resources.getString("char.comma"),voice);
 						break;
 					case ';' :
-						NarratorFactory.getInstance().speak(resources.getString("char.semi_colon"));
+						NarratorFactory.getInstance().speak(resources.getString("char.semi_colon"),voice);
 						break;
 					case ':' :
-						NarratorFactory.getInstance().speak(resources.getString("char.colon"));
+						NarratorFactory.getInstance().speak(resources.getString("char.colon"),voice);
 						break;	
 					case '<' :
-						NarratorFactory.getInstance().speak(resources.getString("char.lower_than"));
+						NarratorFactory.getInstance().speak(resources.getString("char.lower_than"),voice);
 						break;
 					case '>' :
-						NarratorFactory.getInstance().speak(resources.getString("char.greater_than"));
+						NarratorFactory.getInstance().speak(resources.getString("char.greater_than"),voice);
 						break;
 					case '#' :
-						NarratorFactory.getInstance().speak(resources.getString("char.sharp"));
+						NarratorFactory.getInstance().speak(resources.getString("char.sharp"),voice);
 						break;
 					case '~' : 
-						NarratorFactory.getInstance().speak(resources.getString("char.tilde"));
+						NarratorFactory.getInstance().speak(resources.getString("char.tilde"),voice);
 						break;
 					case '+' : 
-						NarratorFactory.getInstance().speak(resources.getString("char.plus"));
+						NarratorFactory.getInstance().speak(resources.getString("char.plus"),voice);
 						break;
 					case '-' : 
-						NarratorFactory.getInstance().speak(resources.getString("char.dash"));
+						NarratorFactory.getInstance().speak(resources.getString("char.dash"),voice);
 						break;
 					case '_' : 
-						NarratorFactory.getInstance().speak(resources.getString("char.underscore"));
+						NarratorFactory.getInstance().speak(resources.getString("char.underscore"),voice);
 						break;	
 					case '/' :
-						NarratorFactory.getInstance().speak(resources.getString("char.slash"));
+						NarratorFactory.getInstance().speak(resources.getString("char.slash"),voice);
 						break;
 					}
 				}
@@ -261,10 +296,10 @@
 			
 			switch(evt.getKeyCode()){
 			case KeyEvent.VK_BACK_SPACE:
-				NarratorFactory.getInstance().speak(resources.getString("char.back_space"));
+				NarratorFactory.getInstance().speak(resources.getString("char.back_space"),voice);
 				break;
 			case KeyEvent.VK_DELETE :
-				NarratorFactory.getInstance().speak(resources.getString("char.delete"));
+				NarratorFactory.getInstance().speak(resources.getString("char.delete"),voice);
 				break;
 			case KeyEvent.VK_LEFT :
 			case KeyEvent.VK_RIGHT :
@@ -280,7 +315,7 @@
 							return;
 						}
 					}
-					NarratorFactory.getInstance().speak(textComponent.getText(textComponent.getCaretPosition(),1));
+					NarratorFactory.getInstance().speak(textComponent.getText(textComponent.getCaretPosition(),1),voice);
 				} catch (BadLocationException e1) {
 					e1.printStackTrace();
 				}
@@ -311,7 +346,7 @@
 				
 				if(end == begin)//in case it's an empty line
 					end++;
-				NarratorFactory.getInstance().speak(text.substring(begin, end));
+				NarratorFactory.getInstance().speak(text.substring(begin, end),voice);
 				break;
 			case KeyEvent.VK_DOWN :
 				if(isLastLine){ //no new line we either have one line only or sit on the last one
@@ -335,14 +370,14 @@
 				
 				if(end == begin) // in case it's an empty line
 					end++;
-				NarratorFactory.getInstance().speak(text.substring(begin, end));
+				NarratorFactory.getInstance().speak(text.substring(begin, end),voice);
 				break;
 			}
 		}
 	}
 	
 	private static final ResourceBundle resources = ResourceBundle.getBundle(Narrator.class.getName());
-	private static final SpeechKeyListener speechKeyListener = new SpeechKeyListener(true);
+	private static final SpeechKeyListener speechKeyListener = new SpeechKeyListener(true,false);
 	private static final ItemListener comboBoxItemListener = new ItemListener(){
 		@Override
 		public void itemStateChanged(ItemEvent evt) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/TreeSonifier.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,163 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+package uk.ac.qmul.eecs.ccmi.speech;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JTree;
+import javax.swing.KeyStroke;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+import uk.ac.qmul.eecs.ccmi.sound.PlayerListener;
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+
+/**
+ *
+ */
+@SuppressWarnings("serial")
+public class TreeSonifier {
+
+	public void sonify(final JTree tree){
+		/* select the root node as selected */ 
+		tree.setSelectionRow(0);
+		/* Overwrite keystrokes up,down,left,right arrows and space, shift, ctrl */
+		   tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,0),"down");
+		   tree.getActionMap().put("down", new AbstractAction(){
+				@Override
+				public void actionPerformed(ActionEvent evt) {
+					DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
+					/* look if we've got a sibling node after (we are not at the bottom) */
+					DefaultMutableTreeNode nextTreeNode = treeNode.getNextSibling(); 
+					SoundEvent loop = null;
+					if(nextTreeNode == null){
+						DefaultMutableTreeNode parent = (DefaultMutableTreeNode)treeNode.getParent();
+						if(parent == null) /* root node, just stay there */
+							nextTreeNode = treeNode;
+						else /* loop = go to first child of own parent */
+							nextTreeNode = (DefaultMutableTreeNode)parent.getFirstChild();
+						loop = SoundEvent.LIST_BOTTOM_REACHED;
+					}
+					tree.setSelectionPath(new TreePath(nextTreeNode.getPath()));
+					final String currentPathSpeech = currentPathSpeech(tree);
+					SoundFactory.getInstance().play(loop, new PlayerListener(){
+						public void playEnded() {
+							NarratorFactory.getInstance().speak(currentPathSpeech);
+						}
+					});
+				}});
+		   
+		   tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP,0),"up");
+		   tree.getActionMap().put("up", new AbstractAction(){
+				@Override
+				public void actionPerformed(ActionEvent evt) {
+					DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
+					DefaultMutableTreeNode previousTreeNode = treeNode.getPreviousSibling();
+					SoundEvent loop = null;
+					if(previousTreeNode == null){
+						DefaultMutableTreeNode parent = (DefaultMutableTreeNode)treeNode.getParent();
+						if(parent == null) /* root node */
+							previousTreeNode = treeNode;
+						else 
+							previousTreeNode = (DefaultMutableTreeNode)parent.getLastChild();
+						loop = SoundEvent.LIST_TOP_REACHED;
+					}
+					tree.setSelectionPath(new TreePath(previousTreeNode.getPath()));
+					final String currentPathSpeech = currentPathSpeech(tree);
+					SoundFactory.getInstance().play(loop, new PlayerListener(){
+						public void playEnded() {
+							NarratorFactory.getInstance().speak(currentPathSpeech);
+						}
+					});
+				}});
+		   
+		   tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0),"right");
+		   tree.getActionMap().put("right", new AbstractAction(){
+			   @Override
+			   public void actionPerformed(ActionEvent evt) {
+				   TreePath path = tree.getSelectionPath();
+				   DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)path.getLastPathComponent();
+				   if(treeNode.isLeaf()){
+					   SoundFactory.getInstance().play(SoundEvent.ERROR);
+				   }
+				   else{
+					   tree.expandPath(path);
+					   tree.setSelectionPath(new TreePath(((DefaultMutableTreeNode)treeNode.getFirstChild()).getPath()));
+					   final String currentPathSpeech = currentPathSpeech(tree);
+					   SoundFactory.getInstance().play(SoundEvent.TREE_NODE_EXPAND,new PlayerListener(){
+						@Override
+						public void playEnded() {
+							NarratorFactory.getInstance().speak(currentPathSpeech);
+						}
+					   });
+				   }
+			   }
+		   });
+		   
+		   tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0),"left");
+		   tree.getActionMap().put("left", new AbstractAction(){
+			   @Override
+			   public void actionPerformed(ActionEvent evt) {
+				   TreePath path = tree.getSelectionPath();
+				   DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)path.getLastPathComponent();
+				   DefaultMutableTreeNode parent = (DefaultMutableTreeNode)treeNode.getParent();
+				   if(parent == null){/* root node */
+					   SoundFactory.getInstance().play(SoundEvent.ERROR);
+				   }
+				   else{
+					   TreePath newPath = new TreePath(((DefaultMutableTreeNode)parent).getPath());
+					   tree.setSelectionPath(newPath);
+					   tree.collapsePath(newPath);
+					   final String currentPathSpeech = currentPathSpeech(tree); 
+					   SoundFactory.getInstance().play(SoundEvent.TREE_NODE_COLLAPSE,new PlayerListener(){
+							@Override
+							public void playEnded() {
+								NarratorFactory.getInstance().speak(currentPathSpeech);
+							}
+					   });
+				   }
+			   }
+		   });
+		   
+		   tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0),"space");
+		   tree.getActionMap().put("space",new AbstractAction(){
+			   @Override
+			   public void actionPerformed(ActionEvent arg0) {
+				   space(tree);
+			   }
+		   });
+		   /* make the tree ignore the page up and page down keys */
+		   tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0),"none");
+		   tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0),"none");
+	}
+
+	protected String currentPathSpeech(JTree tree) {
+		TreePath path =	tree.getSelectionPath();
+		DefaultMutableTreeNode selectedPathTreeNode = (DefaultMutableTreeNode)path.getLastPathComponent();
+		return selectedPathTreeNode.toString();
+	}
+	
+	protected void space(JTree tree){
+		NarratorFactory.getInstance().speak(currentPathSpeech(tree));
+	}
+	
+}
Binary file java/src/uk/ac/qmul/eecs/ccmi/speech/WinNarrator.dll has changed
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/GridBagUtilities.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/GridBagUtilities.java	Wed Apr 25 17:09:09 2012 +0100
@@ -35,6 +35,13 @@
 		 row = 0;
 	}
 	
+	/**
+	 * Provides the {@code GridBagConstrains} for a label. The label is placed
+	 * on the left
+	 * @param pad the pad between the label and the left margin of the component containing
+	 * it 
+	 * @return a {@code GridBagConstrains} object to pass to the {@code add} method of {@code JComponent}
+	 */
 	public GridBagConstraints label(int pad){
 		GridBagConstraints c ;
 		
@@ -47,10 +54,20 @@
 		return c;
 	}
 	
+	/**
+	 * Equivalent to {@link #label(int)} passing as argument the value previously 
+	 * set by {@link #setLabelPad(int)} or {@link #DEFAULT_LABEL_PAD} otherwise.
+	 * @return
+	 */
 	public GridBagConstraints label(){
 		return label(labelPad);
 	}
 	
+	/**
+	 * Sets the value used by {@link #label()} as the pad between the label 
+	 * and the left margin of the component containing it 
+	 * @param labelPad the label pad 
+	 */
 	public void setLabelPad(int labelPad){
 		this.labelPad = labelPad;
 	}
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/InteractionLog.java	Mon Feb 06 12:54:06 2012 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/InteractionLog.java	Wed Apr 25 17:09:09 2012 +0100
@@ -43,7 +43,7 @@
 		}
 		
 		/* also print the log on the console */
-		java.util.logging.ConsoleHandler ch = new java.util.logging.ConsoleHandler(); 
+		java.util.logging.ConsoleHandler ch = new java.util.logging.ConsoleHandler();
 		ch.setLevel(Level.ALL);
 		ch.setFormatter(new CCmILogFormatter());
 		logger.addHandler(ch);
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/NativeLibFileWriter.java	Mon Feb 06 12:54:06 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*  
- CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
-  
- Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/  
-
-package uk.ac.qmul.eecs.ccmi.utils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-/**
- * This class is used to extract a native library (e.g. .dll file in windows) from within a jar 
- * to the local file system, in order to allow the virtual machine to load it.
- *
- */
-public class NativeLibFileWriter {
-	
-	/**
-	 * Creates an instance of the the class linked to a native library file.
-	 * @param resource the URL of the native library file. The URL can be obtained by  
-	 * @see Class#getResource(String), therefore can be called from a class within a jar file
-	 * which needs to access a static library.  
-	 */
-	public NativeLibFileWriter(URL resource){
-		this.resource = resource;
-	}
-
-	/**
-	 * Writes the file in a directory returned by {@link PreferencesService#get(String, String)}} if defined, or 
-	 * the System default temporary directory otherwise. 
-	 * The path to the file can be retrieved by @see {@link #getFilePath()} and then passed as argument 
-	 * to {@link System#load(String)} 
-	 * 
-	 * @param prefix a prefix the temporary native library file will have in the temporary directory  
-	 */
-	public void writeToDisk(String fileName){
-		InputStream in = null;
-		FileOutputStream out = null;
-		File lib = new File(PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),fileName);
-		if(lib.exists()){ //if dll already exists. no job needs to be done.
-			path = lib.getAbsolutePath();
-			return;
-		}
-		try{
-			in = resource.openStream();
-			out = new FileOutputStream(lib);
-			int byteRead;
-			byte[] b = new byte[1024];
-			while((byteRead = in.read(b)) > 0){
-				out.write(b, 0, byteRead);
-			}
-			path = lib.getAbsolutePath();
-		}catch(IOException ioe){
-			path = null;
-		}finally{
-			if(in != null) 
-				try{in.close();}catch(IOException ioe){}
-			if(out != null) 
-				try{out.close();}catch(IOException ioe){}	
-		}
-		
-	}
-	
-	public String getFilePath(){
-		return path;
-	}
-	
-	private URL resource;
-	private String path;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/ResourceFileWriter.java	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,99 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+
+package uk.ac.qmul.eecs.ccmi.utils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * This class is used to extract a native library (e.g. .dll file in windows) from within a jar 
+ * to the local file system, in order to allow the virtual machine to load it.
+ *
+ */
+public class ResourceFileWriter {
+	
+	/**
+	 * Creates an instance of the the class linked to a native library file.
+	 * @param resource the URL of the native library file. The URL can be obtained by  
+	 * @see Class#getResource(String), therefore can be called from a class within a jar file
+	 * which needs to access a static library.  
+	 */
+	public ResourceFileWriter(URL resource){
+		this.resource = resource;
+	}
+	
+	public void serResource(URL resource){
+		this.resource = resource;;
+		path = null;
+	}
+
+	/**
+	 * Writes the file in a directory returned by {@link PreferencesService#get(String, String)}} if defined, or 
+	 * the System default temporary directory otherwise. 
+	 * The path to the file can be retrieved by @see {@link #getFilePath()} and then passed as argument 
+	 * to {@link System#load(String)} 
+	 * 
+	 * @param prefix a prefix the temporary native library file will have in the temporary directory  
+	 */
+	public void writeToDisk(String dir,String fileName){
+		if (resource == null) 
+			return;
+		InputStream in = null;
+		FileOutputStream out = null;
+		File file = new File(dir,fileName);
+		if(file.exists()){ //if file already exists. no job needs to be done.
+			path = file.getAbsolutePath();
+			return;
+		}
+		try{
+			in = resource.openStream();
+			out = new FileOutputStream(file);
+			int byteRead;
+			byte[] b = new byte[1024];
+			while((byteRead = in.read(b)) > 0){
+				out.write(b, 0, byteRead);
+			}
+			path = file.getAbsolutePath();
+		}catch(IOException ioe){
+			path = null;
+		}finally{
+			if(in != null) 
+				try{in.close();}catch(IOException ioe){}
+			if(out != null) 
+				try{out.close();}catch(IOException ioe){}	
+		}
+		
+	}
+	
+	/**
+	 * Returns the absolute path of the last written file. If the writing wasn't successfully 
+	 * or no writing took place yet, then {@code null} is returned. 
+	 * @return the path of the last written file or {@code null}
+	 */
+	public String getFilePath(){
+		return path;
+	}
+	
+	private URL resource;
+	private String path;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/CollectionsManager.cpp	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,341 @@
+#include "CollectionsManager.h"
+
+void CollectionsManager::init(void){
+	/**************** init jni variables **********************/
+	
+	// --- classes --- 
+	// get the java haptics class 
+	hapticClass = env->GetObjectClass(*haptics);
+	if(hapticClass == NULL){
+		stopExecution("Could not find the Haptics class");
+	}
+
+	bitsetClass = env->FindClass("Ljava/util/BitSet;");
+	if(bitsetClass == NULL)
+		stopExecution("failed to find bitset class");
+
+	//get the node list class 
+	listClass = env->FindClass("Ljava/util/ArrayList;");
+	if(listClass == NULL)
+		stopExecution("failed to find list class");
+
+	// get the Node class to call the get method on the node list 
+	hapticNodeClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/Node;");
+	if(hapticNodeClass == NULL){
+		stopExecution("Could not find the Node class");
+	}
+
+	// get the Edge class to call the get method on the edge list 
+	hapticEdgeClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/Edge;");
+	if(hapticEdgeClass == NULL){
+		stopExecution("Could not find the Edge class");
+	}
+	// --- methods ---
+	// "get" method id 
+	getMethodId = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
+	if(getMethodId == NULL)
+		stopExecution("Could not retrieve the get method id");
+
+	//size method id
+	sizeMethodId = env->GetMethodID(listClass, "size", "()I");
+	if(sizeMethodId == NULL)
+		stopExecution("failed to get size method id");
+
+	//get method of the BitSet class
+	getBitMethodId = env->GetMethodID(bitsetClass, "get","(I)Z");
+	if(getBitMethodId == NULL)
+		stopExecution("failed to get the get method id of the BitSet class");
+
+	getNodeFromIDMethodId = env->GetMethodID(hapticClass,"getNodeFromID","(I)Luk/ac/qmul/eecs/ccmi/haptics/Node;");
+	if(getNodeFromIDMethodId == NULL){
+		stopExecution("failed to get getNodeFromID of the Haptics class");
+	}
+
+	getEdgeFromIDMethodId = env->GetMethodID(hapticClass,"getEdgeFromID","(I)Luk/ac/qmul/eecs/ccmi/haptics/Edge;"); 
+	if(getEdgeFromIDMethodId == NULL){
+		stopExecution("failed to get getEdgeFromID of the Haptics class");
+	}
+
+	// -- field id's --
+	//retrieve edgeList field id
+	edgeListfieldId = env->GetFieldID(hapticClass,"currentEdges", "Ljava/util/ArrayList;");
+	if(edgeListfieldId == NULL){
+		stopExecution("failed to find the edge list field id"); 
+	}
+	
+	//retrieve nodeList field id
+	nodeListfieldId = env->GetFieldID(hapticClass,"currentNodes", "Ljava/util/ArrayList;");
+	if(nodeListfieldId == NULL){
+		stopExecution("failed to find the node list field id");
+	}
+
+	// Node fields id's 
+	xFieldId = env->GetFieldID(hapticNodeClass,"x","D");
+	if(xFieldId == NULL)
+		stopExecution("Could not find the x field ID");
+	
+
+	yFieldId = env->GetFieldID(hapticNodeClass,"y","D");
+	if(yFieldId == NULL)
+		stopExecution("Could not find the y field ID");
+	
+	nodeHapticIdFieldId = env->GetFieldID(hapticNodeClass, "hapticId", "I");
+	if(nodeHapticIdFieldId == NULL)
+		stopExecution("Could not find the node hapticId field ID");
+
+	nodeDiagramIdFieldId = env->GetFieldID(hapticNodeClass, "diagramId", "I");
+	if(nodeDiagramIdFieldId == NULL) 
+		stopExecution("Could not find the node diagramId field ID");
+	
+	// Edge field id's
+	// edge.hapticId
+	edgeHapticIdFieldId = env->GetFieldID(hapticEdgeClass, "hapticId", "I");
+	if(edgeHapticIdFieldId == NULL)
+		stopExecution("Could not find the edge hapticId field ID");
+
+	edgeDiagramIdFieldId = env->GetFieldID(hapticEdgeClass, "diagramId", "I");
+	if(edgeDiagramIdFieldId == NULL)
+		stopExecution("Could not find the edge diagramId field ID");
+
+	// stipplePattern
+	stipplePatternfieldId = env->GetFieldID(hapticEdgeClass, "stipplePattern", "I");
+	if(stipplePatternfieldId == NULL){
+		stopExecution("Could not find the stipplePattern field ID");
+	}
+
+	edgeSizefieldId = env->GetFieldID(hapticEdgeClass, "size", "I");
+	if(edgeSizefieldId == NULL)
+		stopExecution("Could not find edge size field ID");
+
+	edgeXsFieldId = env->GetFieldID(hapticEdgeClass,"xs", "[D");
+	if(edgeXsFieldId == NULL)
+		stopExecution("Could not find edge xs field ID");
+	
+	edgeYsFieldId = env->GetFieldID(hapticEdgeClass,"ys", "[D");
+	if(edgeYsFieldId == NULL)
+		stopExecution("Could not find edge ys field ID");
+
+	edgeAdjMatrixFieldId = env->GetFieldID(hapticEdgeClass,"adjMatrix", "[Ljava/util/BitSet;");
+	if(edgeAdjMatrixFieldId == NULL)
+		stopExecution("Could not find edge adjMatrix field ID");
+
+	attractPointXFieldId = env->GetFieldID(hapticEdgeClass, "attractPointX", "D");
+	if(attractPointXFieldId == NULL) 
+		stopExecution("Could not find the edge attractPointX field ID");
+
+	attractPointYFieldId = env->GetFieldID(hapticEdgeClass, "attractPointY", "D");
+	if(attractPointYFieldId == NULL) 
+		stopExecution("Could not find the edge attractPointY field ID");
+
+	edgeNodeStartFieldId = env->GetFieldID(hapticEdgeClass, "nodeStart", "I");
+	if(edgeNodeStartFieldId == NULL)
+		stopExecution("Could not find the edge nodeStart field ID");
+}
+
+const jint CollectionsManager::getNodesNum() {
+	/* to read the node list field into the nodeList variable each time we get the size 
+	 * is needed as, when the tab is switched the nodeList variables still points to the 
+	 * previuos tab's node list. We do it only here and not in getNodeData because
+	 * getNodesData follows this call before releasing the monitor, therefore 
+	 * data integrity is granted. 
+	 */
+	env->DeleteLocalRef(nodeList);
+	nodeList = env->GetObjectField(*haptics, nodeListfieldId);
+	if(nodeList == NULL){
+		stopExecution("could not get the node list field of Haptic Class");
+	}
+
+	jint size =  env->CallIntMethod( nodeList, sizeMethodId);
+	checkExceptions(env,"Could not call ArrayList<Node>.size()");
+	return size;
+}
+
+const jint CollectionsManager::getEdgesNum(){
+	env->DeleteLocalRef(edgeList);
+	edgeList = env->GetObjectField(*haptics, edgeListfieldId);
+	if(edgeList == NULL){
+		stopExecution("could not get the edge list field of Haptic Class");
+	}
+
+	jint size = env->CallIntMethod( edgeList, sizeMethodId);
+	checkExceptions(env, "Could not call ArrayList<Edge>.size()");
+	return size;
+}
+
+CollectionsManager::NodeData & CollectionsManager::getNodeData(const int i){
+	// get the i-th node
+	jobject currentNode = env->CallObjectMethod(nodeList,getMethodId,i);
+	checkExceptions(env,"Could not call ArrayList<Node>.size()");
+	fillupNodeData(nd,currentNode); 
+	env->DeleteLocalRef(currentNode);
+	return nd;
+}
+
+CollectionsManager::EdgeData & CollectionsManager::getEdgeData(const int i){
+	/* first we look for the i-th edge in the Haptics java class. Once we get it, 
+	 * we need all the coordinates of the node this edge is connecting, so that we 
+	 * can draw it. The edge mantains a list of references to such nodes.
+	 */
+
+	// get the i-th edge
+	jobject currentEdge = env->CallObjectMethod(edgeList,getMethodId,i);
+	checkExceptions(env, "Could not call ArrayList<Edge>.get(int) in the haptics edges");
+	if(currentEdge == NULL)
+		stopExecution("Could not find get current Edge");
+	jint size = env->GetIntField(currentEdge, edgeSizefieldId);
+	ed.setSize(size); 
+	fillupEdgeData(ed,currentEdge);	
+	env->DeleteLocalRef(currentEdge);
+	return ed;
+}
+
+bool CollectionsManager::isNode(const jint id) const{
+	if(env->MonitorEnter(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	jobject currentNode = env->CallObjectMethod(*haptics,getNodeFromIDMethodId,id);
+	checkExceptions(env, "Could not call Haptics.getNodeFromIDMethodId in CollectionsManager::isNode");
+	bool retVal = (currentNode != NULL);
+	env->DeleteLocalRef(currentNode);
+	if(env->MonitorExit(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	return retVal;
+}
+
+CollectionsManager::NodeData & CollectionsManager::getNodeDataFromID(const jint id){
+	if(env->MonitorEnter(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	jobject currentNode = env->CallObjectMethod(*haptics,getNodeFromIDMethodId,id);
+	checkExceptions(env, "Could not call Haptics.getNodeFromIDMethodId in CollectionsManager::getNodeDataFromID");
+	if(currentNode == NULL){
+		env->DeleteLocalRef(currentNode);
+		throw(NULL);
+	}
+	fillupNodeData(nd,currentNode);
+	env->DeleteLocalRef(currentNode);
+	if(env->MonitorExit(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	return nd;
+}
+
+bool CollectionsManager::isEdge(const jint id) const{
+	if(env->MonitorEnter(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	jobject currentEdge = env->CallObjectMethod(*haptics,getEdgeFromIDMethodId,id);
+	checkExceptions(env, "Could not call Haptics.getEdgeFromIDMethodId in CollectionsManager::isEdge");
+	bool retVal = (currentEdge != NULL);
+	env->DeleteLocalRef(currentEdge);
+	if(env->MonitorExit(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	return retVal;
+}
+
+CollectionsManager::EdgeData & CollectionsManager::getEdgeDataFromID(const jint id){
+	if(env->MonitorEnter(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	jobject currentEdge = env->CallObjectMethod(*haptics,getEdgeFromIDMethodId,id);
+	checkExceptions(env, "Could not call Haptics.getEdgeFromIDMethodId in CollectionsManager::getEdgeDataFromID");
+	if(currentEdge == NULL){
+		env->DeleteLocalRef(currentEdge);
+		throw(NULL);	
+	}
+
+	jint size = env->GetIntField(currentEdge, edgeSizefieldId);
+	ed.setSize(size); 
+	fillupEdgeData(ed,currentEdge);
+	env->DeleteLocalRef(currentEdge);
+	if(env->MonitorExit(*haptics) != JNI_OK){
+		throw(NULL);
+	}
+	return ed;
+}
+
+void CollectionsManager::fillupNodeData(NodeData & nd, jobject & currentNode){
+	//reads the fields of the current node 
+	nd.x = env->GetDoubleField(currentNode,xFieldId);
+	nd.y = env->GetDoubleField(currentNode,yFieldId);
+	hduVector3Dd glCoordinatePosition;
+    // takes coordinates from the screen. Needs to convert the y axis as in openGL (0,0) = bottom left corner
+	fromScreen(hduVector3Dd(nd.x, screenHeight - nd.y, 0),glCoordinatePosition);
+	nd.x = glCoordinatePosition[0];
+	nd.y = glCoordinatePosition[1];
+	nd.hapticId = env->GetIntField(currentNode, nodeHapticIdFieldId);
+	nd.diagramId = env->GetIntField(currentNode, nodeDiagramIdFieldId);
+}
+
+void CollectionsManager::fillupEdgeData(EdgeData & ed, jobject & currentEdge){
+	/* get the array of x coordinates */
+	jobject xsAsObj = env->GetObjectField(currentEdge,edgeXsFieldId);
+	if(xsAsObj == NULL)
+		stopExecution("Cannot get the xs field");
+	jdoubleArray *jxs = reinterpret_cast<jdoubleArray*>(&xsAsObj);
+	double * xs = env->GetDoubleArrayElements(*jxs, NULL);
+	if(xs == NULL)
+		stopExecution("Cannot get the xs field array of double");
+
+	/* get the array of y coordinates */
+	jobject ysAsObj = env->GetObjectField(currentEdge,edgeYsFieldId);
+	if(ysAsObj == NULL)
+		stopExecution("Cannot get the xs field");
+		
+	jdoubleArray *jys = reinterpret_cast<jdoubleArray*>(&ysAsObj);
+	double * ys = env->GetDoubleArrayElements(*jys, NULL);
+	if(ys == NULL)
+		stopExecution("Cannot get the ys field array of double");
+	// copy the data into the edgeData object
+	for(unsigned int i=0; i<ed.getSize(); i++){ 
+		ed.x[i] = xs[i];
+		ed.y[i] = ys[i];
+		// takes coordinates from the screen (needs to convert the y axis as in openGL 0 = bottom left corner
+		hduVector3Dd glCoordinatePosition;
+		fromScreen(hduVector3Dd(ed.x[i], screenHeight - ed.y[i], 0),glCoordinatePosition);
+		ed.x[i] = glCoordinatePosition[0];
+		ed.y[i] = glCoordinatePosition[1];
+	}
+	env->ReleaseDoubleArrayElements(*jxs, xs, 0);
+	env->ReleaseDoubleArrayElements(*jys, ys, 0);
+	env->DeleteLocalRef(xsAsObj);
+	env->DeleteLocalRef(ysAsObj);
+	jobject adjMatrixAsObj = env->GetObjectField(currentEdge, edgeAdjMatrixFieldId);
+	if(adjMatrixAsObj == NULL)
+		stopExecution("Cannot get the adjMatrix field");
+	jobjectArray *jadjMatrix = reinterpret_cast<jobjectArray*>(&adjMatrixAsObj);
+	
+	for(unsigned int i=0; i<ed.getSize(); i++){
+		jobject adjMatrixBitSet = env->GetObjectArrayElement(*jadjMatrix,i);
+		if(adjMatrixBitSet == NULL)
+			stopExecution("Cannot get the adjMatrix field array element");
+		for(unsigned int j=0;j<ed.getSize(); j++){
+			jboolean b = env->CallBooleanMethod(adjMatrixBitSet,getBitMethodId,j);
+			checkExceptions(env,"Could not call BitSet.get()");
+			if(b == JNI_TRUE)
+				ed.adjMatrix[i][j] = true;
+			else
+				ed.adjMatrix[i][j] = false;
+		}
+		env->DeleteLocalRef(adjMatrixBitSet);
+	}	
+	env->DeleteLocalRef(adjMatrixAsObj);
+
+	// set the haptic id used by the haptic device 
+	ed.hapticId = env->GetIntField(currentEdge, edgeHapticIdFieldId);
+	// set the diagram id used in the java thread
+	ed.diagramId = env->GetIntField(currentEdge, edgeDiagramIdFieldId);
+	// set the stipple pattern 
+	ed.stipplePattern = env->GetIntField(currentEdge, stipplePatternfieldId);
+	// set the attract point
+	ed.attractPoint[0] = env->GetDoubleField(currentEdge, attractPointXFieldId);
+	ed.attractPoint[1] = env->GetDoubleField(currentEdge, attractPointYFieldId);
+	hduVector3Dd glCoordinatePosition;
+	fromScreen(hduVector3Dd(ed.attractPoint[0], screenHeight - ed.attractPoint[1], 0),glCoordinatePosition);
+	ed.attractPoint[0] = glCoordinatePosition[0];
+	ed.attractPoint[1] = glCoordinatePosition[1];
+	//set the index from which the nodes start in the adjMatrix
+	ed.nodeStart = env->GetIntField(currentEdge, edgeNodeStartFieldId);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/CollectionsManager.h	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,140 @@
+#pragma once
+
+#include <jni.h>
+#include "stdafx.h"
+#include "utils.h"
+
+
+/* this class uses the java haptic object lists to provide nodes *
+ * and edges to draw to the graphic and haptic managers          */
+class CollectionsManager
+{
+public:
+	struct NodeData {
+		jdouble x;
+		jdouble y;
+		jint hapticId;
+		jint diagramId;
+
+		NodeData(){}
+	private :
+		NodeData(const NodeData& n){/* avoid mistakenly copy construction calls */}
+	};
+
+	struct EdgeData{
+		jdouble *x;
+		jdouble *y;
+		bool **adjMatrix;
+		jint hapticId;
+		jint diagramId;
+		jint stipplePattern;
+		jsize nodeStart;
+		jdouble attractPoint[2];
+		void setSize(unsigned int s){
+			size = s;
+			if(s > previousSize){
+				/* delete the old memory */
+				delete [] x;
+				delete [] y;
+				for(unsigned int i=0; i<previousSize; i++)
+					delete[] adjMatrix[i];
+				delete [] adjMatrix;
+				/* allocates a bigger one */
+				try{
+					x = new jdouble[size];
+					y = new jdouble[size];
+					adjMatrix = new  bool* [size];
+					for(unsigned int i=0; i<size; i++){
+						adjMatrix[i] = new bool[size];
+						for(unsigned int j=0; j<size; j++)
+							adjMatrix[i][j] = false;
+					}
+				}catch(std::bad_alloc){
+					stopExecution("Could not allocate memory for the program.\nAborting...");
+				}
+				previousSize = size;
+			}
+		}
+
+		unsigned int getSize() const {
+			return size;
+		}
+
+		EdgeData(unsigned int s) : size(s), previousSize(s){
+			x = new jdouble[size];
+			y = new jdouble[size];
+			adjMatrix = new  bool* [size];
+			for(unsigned int i=0; i<size; i++){
+				adjMatrix[i] = new bool[size];
+				for(unsigned int j=0; j<size; j++)
+					adjMatrix[i][j] = false;
+			}
+		}
+		~EdgeData(){
+			delete [] x;
+			delete [] y;
+			for(unsigned int i=0; i<size; i++)
+				delete[] adjMatrix[i];
+			delete [] adjMatrix;
+		}
+	private :
+		unsigned int size;
+		unsigned int previousSize;
+		EdgeData(const EdgeData& e){ /* avoid mistakenly copy construction */}
+
+	};
+private:
+	JNIEnv *env;
+	jobject *haptics;
+
+	jclass hapticClass;
+	jclass hapticNodeClass;
+	jclass hapticEdgeClass;
+	jclass listClass;
+	jclass bitsetClass;
+	jobject nodeList;
+	jobject edgeList;
+	jmethodID getMethodId;
+	jmethodID sizeMethodId;
+	jmethodID getBitMethodId;
+	jmethodID getNodeFromIDMethodId;
+	jmethodID getEdgeFromIDMethodId;
+	jfieldID xFieldId;
+	jfieldID yFieldId;
+	jfieldID nodeHapticIdFieldId;
+	jfieldID edgeHapticIdFieldId;
+	jfieldID nodeDiagramIdFieldId;
+	jfieldID edgeDiagramIdFieldId;
+	jfieldID nodeListfieldId;
+	jfieldID stipplePatternfieldId;
+	jfieldID edgeListfieldId;
+	jfieldID edgeSizefieldId;
+	jfieldID edgeXsFieldId;
+	jfieldID edgeYsFieldId;
+	jfieldID edgeAdjMatrixFieldId;
+	jfieldID attractPointXFieldId;
+	jfieldID attractPointYFieldId;
+	jfieldID edgeNodeStartFieldId;
+
+	int screenHeight;
+	NodeData nd;
+	EdgeData ed;
+	
+	void fillupNodeData(struct NodeData & nd, jobject & currentNode);
+	void fillupEdgeData(struct EdgeData & ed, jobject & currentEdge);
+public:
+	CollectionsManager(JNIEnv *environment, jobject *obj) : env(environment), haptics(obj), ed(2){}
+	virtual ~CollectionsManager(void){}
+	const jint getNodesNum();
+	const jint getEdgesNum();
+	struct NodeData & getNodeData(const int i);
+	struct EdgeData & getEdgeData(const int i);
+	struct NodeData & getNodeDataFromID(const jint id) throw(int);
+	struct EdgeData & getEdgeDataFromID(const jint id) throw(int);
+	bool isEdge(const jint id) const throw(int);
+	bool isNode(const jint id) const throw(int);
+	void init(void);
+	int getScreenHeight() const { return screenHeight;}
+	void setScreenHeight(const int sh) { screenHeight = sh; }
+	
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/GraphicManager.cpp	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,143 @@
+#include "GraphicManager.h"
+
+void GraphicManager::init(void){
+
+    // Enable depth buffering for hidden surface removal.
+    glDepthFunc(GL_LEQUAL);
+    glEnable(GL_DEPTH_TEST);
+    
+    // Cull back faces.
+    glCullFace(GL_BACK);
+    glEnable(GL_CULL_FACE);
+    
+    // Setup other misc features.
+    glEnable(GL_LIGHTING);
+    glEnable(GL_NORMALIZE);
+    glShadeModel(GL_SMOOTH);
+    
+    // Setup lighting model.
+    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
+    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);    
+    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, light_model_ambient);
+    glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
+    glLightfv(GL_LIGHT0, GL_POSITION, light0_direction);
+    glEnable(GL_LIGHT0);  
+
+	
+}
+
+void GraphicManager::draw(void){
+	 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   
+    // Draw 3D cursor at haptic device position.
+    drawCursor();
+
+	glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT);
+	glEnable(GL_COLOR_MATERIAL);
+	   
+	// Draw each of the nodes as lit sphere.
+	int numNodes = collectionsManager->getNodesNum();
+    GLdouble dPointSize;
+	
+	for ( int i = 0; i < numNodes; i++){
+		dPointSize = nodeSize;
+        glColor3dv(nodeColor);
+			
+		glPushMatrix();
+		CollectionsManager::NodeData & nd = collectionsManager->getNodeData(i); 
+		glTranslated(nd.x,nd.y, 0);
+		double dPointScale = dPointSize * gWorldScale;
+		glScaled(dPointScale, dPointScale, dPointScale);        
+		/* draw shpere */
+		glutSolidSphere(0.5, 10, 10);
+        glPopMatrix();
+    }
+
+	//draw edges
+	glColor3f(1.0, 0.0, 0.0);
+    glLineWidth(2.0);
+	glPushAttrib(GL_ENABLE_BIT);
+	glDisable(GL_LIGHTING);  
+	glEnable(GL_COLOR_MATERIAL);
+	
+	int numEdges = collectionsManager->getEdgesNum();
+	for(int i = 0; i < numEdges; i++){
+		CollectionsManager::EdgeData & ed = collectionsManager->getEdgeData(i);
+		glPushAttrib(GL_ENABLE_BIT);
+		glLineStipple(1, ed.stipplePattern);
+		glEnable(GL_LINE_STIPPLE);
+		glBegin(GL_LINES);
+		for(unsigned int j = 0; j < ed.getSize(); j++){
+			for(unsigned int k = j; k < ed.getSize(); k++){
+				if(ed.adjMatrix[j][k]){
+					glVertex3d(ed.x[j],ed.y[j],0);
+					glVertex3d(ed.x[k],ed.y[k],0);
+				}
+			}
+		}
+		glEnd();		
+		glPopAttrib();
+	}
+    glPopAttrib();
+	glPopMatrix();
+
+}
+
+void GraphicManager::drawCursor(){
+    HLdouble proxyxform[16];
+
+    GLUquadricObj *qobj = 0;
+
+    glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT | GL_LIGHTING_BIT);
+    glPushMatrix();
+    if (!gCursorDisplayList){
+        gCursorDisplayList = glGenLists(1);
+        glNewList(gCursorDisplayList, GL_COMPILE);
+        qobj = gluNewQuadric();
+               
+        gluCylinder(qobj, 0.0, kCursorRadius, kCursorHeight,
+                    kCursorTess, kCursorTess);
+        glTranslated(0.0, 0.0, kCursorHeight);
+        gluCylinder(qobj, kCursorRadius, 0.0, kCursorHeight / 5.0,
+                    kCursorTess, kCursorTess);
+    
+        gluDeleteQuadric(qobj);
+        glEndList();
+    }
+    
+    // Get the proxy transform in world coordinates.
+    hlGetDoublev(HL_PROXY_TRANSFORM, proxyxform);
+    glMultMatrixd(proxyxform);
+
+    // Apply the local cursor scale factor.
+    glScaled(gCursorScale, gCursorScale, gCursorScale);
+	
+    glEnable(GL_COLOR_MATERIAL);
+
+	if(hapticManager->isDraggingNode()){
+		glColor3dv(nodeColor);
+		glutSolidSphere(0.25, 10, 10);
+	}else if(hapticManager->isDraggingEdge()){
+		glColor3dv(edgeColor);
+		glutSolidSphere(0.12,10,10);
+	}
+	glColor3f(0.0, 0.5, 1.0);
+    glCallList(gCursorDisplayList);
+
+    glPopMatrix(); 
+    glPopAttrib();
+}
+
+const double GraphicManager::kCursorRadius = 0.5;
+const double GraphicManager::kCursorHeight = 1.5;
+const int GraphicManager::kCursorTess = 15;
+
+const GLfloat GraphicManager::light_model_ambient[] = {0.3f, 0.3f, 0.3f, 1.0f};
+const GLfloat GraphicManager::light0_diffuse[] = {0.9f, 0.9f, 0.9f, 0.9f};   
+const GLfloat GraphicManager::light0_direction[] = {0.0f, -0.4f, 1.0f, 0.0f}; 
+
+
+const double GraphicManager::nodeSize = 10;
+
+const hduVector3Dd GraphicManager::nodeColor(1.0, 1.0, 1.0);
+const hduVector3Dd GraphicManager::edgeColor(1.0,0.0,0.0);
+	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/GraphicManager.h	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "stdafx.h"
+#include "CollectionsManager.h"
+#include "HapticManager.h"
+
+/* The graphic manager draws the diagram openGL scene graphically.  *
+ * In order to get all the data about the diagram it's constructed  *
+ * passing a reference to the CollectionManager                     */
+class GraphicManager{
+	static const double kCursorRadius;
+	static const double kCursorHeight;
+	static const int kCursorTess;
+	static const GLfloat light_model_ambient[];
+    static const GLfloat light0_diffuse[];   
+    static const GLfloat light0_direction[]; 
+	
+	static const double nodeSize;
+
+	static const hduVector3Dd nodeColor;//(1.0, 1.0, 1.0);
+	static const hduVector3Dd edgeColor;
+
+	CollectionsManager * collectionsManager;
+	HapticManager * hapticManager;
+
+	void drawCursor(void);
+public:
+	double gCursorScale;
+	double gWorldScale;
+	GLuint gCursorDisplayList;
+	GraphicManager(CollectionsManager * cManager, HapticManager * hManager) : collectionsManager(cManager), hapticManager(hManager) {
+		gCursorDisplayList = 0;
+		gWorldScale = 1;
+	};
+	~GraphicManager(void){};
+	void init(void);
+	void draw(void);
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/HapticException.cpp	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,11 @@
+#include "HapticException.h"
+
+HapticException::HapticException(void){
+}
+
+HapticException::~HapticException(void){
+}
+
+const char* HapticException::what() const throw(){
+	return "Could not initialize Haptic device";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/HapticException.h	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,14 @@
+#pragma once
+#include <exception>
+
+using namespace std;
+
+/* This exception is thrown by the HapticManager if the initialization of the Haptic 
+ * doesn't succeed. 
+ */
+class HapticException :	public exception {
+public:
+	HapticException(void);
+	virtual ~HapticException(void);
+	virtual const char* what() const throw();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/HapticManager.cpp	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,672 @@
+#include "HapticManager.h"
+
+#define NO_ERROR_SIMUL
+
+
+void checkExceptions(void);
+void stopExecution(char* msg);
+
+bool movedNode = false;
+
+
+void HLCALLBACK HapticManager::hlButton1CB(HLenum event, HLuint object,
+                                         HLenum thread, HLcache *cache, 
+										 void *userdata)
+{
+	HapticManager *pThis = static_cast<HapticManager*>(userdata);
+	if(object == pThis->wall1Id||object == pThis->wall2Id) // no event is associated to the wall 
+		return;
+	if(event != HL_EVENT_1BUTTONDOWN)
+		return;
+
+	/* check if it was a double click or not */
+	
+	clock_t time = clock();
+	if(pThis->clickStart == 0) // first click
+		pThis->clickStart = time; 
+	else if(time < (pThis->clickStart + CLICK_INTERVAL))
+		pThis->doubleClick = true;
+	
+}
+
+void HLCALLBACK HapticManager::hlButton2CB(HLenum event, HLuint object,
+                                         HLenum thread, HLcache *cache, 
+										 void *userdata){
+	HapticManager *pThis = static_cast<HapticManager*>(userdata);
+	if(event == HL_EVENT_2BUTTONDOWN){
+		pThis->stickyMode = !pThis->stickyMode;
+		pThis->executeCommand('g', pThis->stickyMode ? 1 : 0,0,0,0,0);
+		/* we change mode to stickyMode, thus dragging becomes not active */
+		if(pThis->stickyMode && pThis->springStatus == DRAGGING){
+				pThis->springStatus = STOP_DRAGGING;
+				pThis->secondClick = false;
+		}
+#ifdef VIBRATION
+		// vibrate 
+		if(pThis->vibrationStatus == NO_VIBRATION){
+			pThis->vibrationStatus = START_VIBRATION;
+		}
+#endif
+	}
+}
+
+void HLCALLBACK HapticManager::hlTouchCB(HLenum event, HLuint object, HLenum thread,
+										 HLcache *cache, void *userdata)
+{
+	HapticManager *pThis = static_cast<HapticManager*>(userdata);
+	if(object == pThis->wall1Id||object == pThis->wall2Id) // no event is associated to the wall 
+		return;
+	if(pThis->lastMovedElement == object){
+		pThis->stillTouchingLastMovedElement = true;
+	}
+	
+	try{
+		// check if the touched object is an edge
+		if(pThis->collectionsManager->isEdge(object)){
+			CollectionsManager::EdgeData & ed = pThis->collectionsManager->getEdgeDataFromID(object);
+			pThis->lastTouchedEdgeID = object;
+			pThis->lastTouchedEdgeIsUntouched = false;
+			pThis->lastTouchedEdgeStipplePattern = ed.stipplePattern;
+			// check if the proxy is still touching the edge's node, in that case only play the sound
+			HLboolean isTouching = HL_FALSE;
+			if(pThis->lastTouchedNodeID != HL_OBJECT_ANY){
+				hlGetShapeBooleanv(pThis->lastTouchedNodeID,HL_PROXY_IS_TOUCHING,&isTouching);
+			}
+			/* play the sound and speech associated to the edge */
+			if((isTouching == HL_TRUE||pThis->stillTouchingLastMovedElement) && (object != pThis->attractToHapticId)){
+				if(!pThis->stickyMode)
+					pThis->executeCommand('p',ed.diagramId,0,0,0,0); // just plays the sound
+			}else{
+				pThis->executeCommand('s',ed.diagramId,0,0,0,0);
+				pThis->lastHighlightElementID = object;
+				pThis->executeCommand('t',ed.diagramId,0,0,0,0); // speaks the edge's name
+			}
+		}else{ // it's a node 
+			pThis->lastTouchedNodeID = object;
+			CollectionsManager::NodeData & nd = pThis->collectionsManager->getNodeDataFromID(object);
+			pThis->lastTouchedNodePosition[0] = nd.x;
+			pThis->lastTouchedNodePosition[1] = nd.y;
+			pThis->lastTouchedNodePosition[2] = 0;
+			pThis->frictionDisabled = true;
+			pThis->executeCommand('s',nd.diagramId,0,0,0,0);
+			pThis->lastHighlightElementID = object;
+			pThis->executeCommand('t',nd.diagramId,0,0,0,0);
+		}	
+	}catch(int){
+		stopExecution("Touch Call Back: Could not retrieve element from ID");
+	}
+}
+
+void HLCALLBACK HapticManager::hlUntouchCB(HLenum event, HLuint object, HLenum thread,
+										   HLcache *cache, void *userdata)
+{
+	HapticManager *pThis = static_cast<HapticManager*>(userdata);
+	if(object == pThis->wall1Id||object == pThis->wall2Id) // no event is associated to the wall 
+		return;
+	
+	if(object == pThis->lastMovedElement){
+		pThis->lastMovedElement = HL_OBJECT_ANY;
+		pThis->stillTouchingLastMovedElement = false;
+	}
+	if(pThis->lastHighlightElementID == object){
+		pThis->lastHighlightElementID = HL_OBJECT_ANY;
+		pThis->executeCommand('u',0,0,0,0,0);
+	}
+	if(pThis->lastTouchedEdgeID == object){
+		pThis->lastTouchedEdgeIsUntouched = true;
+	}
+}
+
+
+void HLCALLBACK HapticManager::hlMotionCB(HLenum event, HLuint object, HLenum thread,
+										   HLcache *cache, void *userdata)
+{
+	HapticManager *pThis = static_cast<HapticManager*>(userdata);
+	
+	/* plays the sound to acknowledge the user they're dragging an element around */
+	static int onceinawhile = 0;
+	if(pThis->springStatus == DRAGGING){
+		onceinawhile = (onceinawhile+1)%5;
+		if(!onceinawhile)
+			pThis->executeCommand('g',3,0,0,0,0);
+	}
+	
+	/* the following code is to play element speech when taking a path through an edge starting from a connected node */
+	
+	/* friction is disabled when we land on a node. If proxy ain't on a node, then just return  */
+	/* else it would play every time we move along the edge even if not detouching from a node  */
+	if(!pThis->frictionDisabled){
+		return;
+	}
+
+	/* calculate the distance between the node, the stylus was on, and the current proxy position after moving out from it*/
+	HLdouble proxyPosition[3];
+	hlGetDoublev(HL_DEVICE_POSITION,proxyPosition);
+	proxyPosition[0] -= pThis->lastTouchedNodePosition[0];
+	proxyPosition[1] -= pThis->lastTouchedNodePosition[1];
+	proxyPosition[2] -= pThis->lastTouchedNodePosition[2];
+	if(pThis->lastTouchedEdgeID != HL_OBJECT_ANY){
+		if(norm(proxyPosition) > EDGE_SPEECH_DISTANCE_WHEN_LEAVING_NODE){
+			pThis->frictionDisabled = false;
+			HLboolean isTouching;
+			hlGetShapeBooleanv(pThis->lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching);
+			if(isTouching == HL_TRUE){
+				try{
+					CollectionsManager::EdgeData & ed = pThis->collectionsManager->getEdgeDataFromID(pThis->lastTouchedEdgeID);
+					pThis->executeCommand('t',ed.diagramId,0,0,0,0);
+					pThis->lastTouchedEdgeID = ed.hapticId;
+					pThis->lastHighlightElementID = ed.hapticId;
+					pThis->executeCommand('s',ed.diagramId,0,0,0,0);
+				}catch(int){
+					stopExecution("Button 1 callback: Could not retrieve edge from ID");
+				}
+			}
+		}
+	}
+}
+
+#ifdef VIBRATION
+void HLCALLBACK HapticManager::vibrationCB(HDdouble force[3], HLcache *cache, void *userdata){
+	static const hduVector3Dd direction( 0, 1, 0 );
+    HDdouble instRate;
+    static HDdouble timer = 0;
+
+	HapticManager *pThis = static_cast<HapticManager*>(userdata);
+
+    /* Use the reciprocal of the instantaneous rate as a timer. */
+    hdGetDoublev(HD_INSTANTANEOUS_UPDATE_RATE, &instRate);
+    timer += 1.0 / instRate;
+
+    /* Apply a sinusoidal force in the direction of motion. */
+    hduVecScale(force, direction, VIBRATION_AMPLITUDE * sin(timer * VIBRATION_FREQUENCY));
+    hdSetDoublev(HD_CURRENT_FORCE, force);
+}
+
+void HLCALLBACK HapticManager::beforeVibrationCB(HLcache *cache, void *userdata){}
+
+void HLCALLBACK HapticManager::afterVibrationCB(HLcache *cache, void *userdata){}
+#endif
+
+void HapticManager::init(void) throw(HapticException){
+	HDErrorInfo error;
+
+    ghHD = hdInitDevice(HD_DEFAULT_DEVICE);
+    if (HD_DEVICE_ERROR(error = hdGetError())){
+        throw HapticException();
+    }
+    
+    ghHLRC = hlCreateContext(ghHD);
+    hlMakeCurrent(ghHLRC);
+
+    // Enable optimization of the viewing parameters when rendering geometry for OpenHaptics.
+    hlEnable(HL_HAPTIC_CAMERA_VIEW);
+
+    hlTouchableFace(HL_FRONT);
+	wall1Id = hlGenShapes(1);
+	wall2Id = hlGenShapes(1);
+	attractToHapticId = HL_OBJECT_ANY;
+	lastTouchedNodeID = HL_OBJECT_ANY;
+	lastTouchedEdgeID = HL_OBJECT_ANY;
+	draggedElementID = HL_OBJECT_ANY;
+	lastHighlightElementID = HL_OBJECT_ANY;
+
+	// initialize effects
+	frictionFX = hlGenEffects(1);
+	vibrationFX = hlGenEffects(1);
+	attractionFX = hlGenEffects(1);
+	springFX = hlGenEffects(1);
+	displayList1 = 0;
+	displayList2 = 0;
+	hlEventd(HL_EVENT_MOTION_LINEAR_TOLERANCE,5);
+	hlEventd(HL_EVENT_MOTION_ANGULAR_TOLERANCE,5);
+
+	// add callbacks 
+	hlAddEventCallback(HL_EVENT_2BUTTONDOWN,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton2CB,this);
+	hlAddEventCallback(HL_EVENT_2BUTTONUP,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton2CB,this);
+	hlAddEventCallback(HL_EVENT_1BUTTONUP,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton1CB,this);
+	hlAddEventCallback(HL_EVENT_1BUTTONDOWN,HL_OBJECT_ANY,HL_CLIENT_THREAD,hlButton1CB,this);
+	hlAddEventCallback(HL_EVENT_TOUCH, HL_OBJECT_ANY, HL_CLIENT_THREAD, hlTouchCB, this);
+	hlAddEventCallback(HL_EVENT_UNTOUCH, HL_OBJECT_ANY, HL_CLIENT_THREAD, hlUntouchCB, this);
+	hlAddEventCallback(HL_EVENT_MOTION, HL_OBJECT_ANY, HL_CLIENT_THREAD, hlMotionCB, this);
+
+#ifdef VIBRATION
+	hlBeginFrame();
+	hlCallback(HL_EFFECT_START,(HLcallbackProc) beforeVibrationCB, this);
+	hlCallback(HL_EFFECT_STOP,(HLcallbackProc) afterVibrationCB, this);
+    hlCallback(HL_EFFECT_COMPUTE_FORCE, (HLcallbackProc) vibrationCB, this);
+	hlEndFrame();
+#endif
+}
+
+HapticManager::ClickStatus HapticManager::getButton1Status(){
+	if(clickStart == 0){
+		return NO_CLICK;
+	}else if(doubleClick){
+		/* double click: set everything back to default value and return DOUBLE_CLICK */
+		clickStart = 0;
+		doubleClick = false;
+		return TWO_CLICK;
+	}else if(clock() >= clickStart + CLICK_INTERVAL || secondClick) {
+		/* one click and enough time elapsed, so that it cannot be a double click */
+		clickStart = 0;
+		return ONE_CLICK;
+	}
+	return NO_CLICK;
+}
+
+void HapticManager::doButton1Click(bool doubleClick){
+	/* DOUBLE CLICK*/
+	if(doubleClick){
+		if(lastTouchedNodeID != HL_OBJECT_ANY){
+			HLboolean isTouching = HL_FALSE;
+			hlGetShapeBooleanv(lastTouchedNodeID,HL_PROXY_IS_TOUCHING,&isTouching); // priority to nodes
+			if(isTouching == HL_TRUE||lastTouchedNodeID == lastMovedElement){
+				try{
+					CollectionsManager::NodeData & nd = collectionsManager->getNodeDataFromID(lastTouchedNodeID);
+					executeCommand('i',nd.diagramId, 0,0,0,0);
+				}catch(int){
+					stopExecution("Button 1 callback: Could not retrieve node from ID");
+				}
+			}else{
+				if(lastTouchedEdgeID != HL_OBJECT_ANY){
+					hlGetShapeBooleanv(lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching);
+					if(isTouching == HL_TRUE||lastTouchedNodeID == lastMovedElement){
+						try{
+							CollectionsManager::EdgeData & ed = collectionsManager->getEdgeDataFromID(lastTouchedEdgeID);
+							executeCommand('i',ed.diagramId, 0,0,0,0);
+						}catch(int){
+							stopExecution("Button 1 callback: Could not retrieve edge from ID");
+						}
+					}
+				}
+			}
+		}
+		// this was a double click, thus clean up the data about the dragging
+		if(springStatus == DRAGGING){
+			springStatus = STOP_DRAGGING;
+			secondClick = false;
+		}
+		return;			
+	}
+	
+	/* - SINLGE CLICK - */
+	if(!secondClick){ //  button 1 pressed for the first time 
+		HLboolean isTouching;
+		/* if the stylus is touching a node then pick it up */
+		/* give priority to nodes */
+		if(lastTouchedNodeID != HL_OBJECT_ANY){
+			hlGetShapeBooleanv(lastTouchedNodeID,HL_PROXY_IS_TOUCHING,&isTouching);
+			if(isTouching == HL_TRUE || (stillTouchingLastMovedElement&&lastMovedElement == lastTouchedNodeID)){
+				draggedElementID = lastTouchedNodeID;
+				try{
+					CollectionsManager::NodeData & nd = collectionsManager->getNodeDataFromID(lastTouchedNodeID);
+					executeCommand('c',nd.diagramId,0,0,0,0); // pick up command
+				}catch(int){
+					stopExecution("Button 1 callback: Could not retrieve node from ID");
+				}
+				return;
+			}
+		}
+		/* no node is currently touched, check the edges now */
+		if(collectionsManager->isEdge(lastTouchedEdgeID)){
+			hlGetShapeBooleanv(lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching);
+			if(isTouching == HL_TRUE||(stillTouchingLastMovedElement&&lastMovedElement == lastTouchedEdgeID)){
+				draggedElementID = lastTouchedEdgeID;
+				try{
+					CollectionsManager::EdgeData & ed = collectionsManager->getEdgeDataFromID(lastTouchedEdgeID);
+					executeCommand('c',ed.diagramId,0,0,0,0); // pick up command
+				}catch(int){
+					stopExecution("Button 1 callback: Could not retrieve edge from ID");
+				}
+			}
+		}
+	}else{ // button 1 pressed for the second time 
+		if(springStatus != DRAGGING)
+			return;
+		springStatus = STOP_DRAGGING;
+		HLdouble leanPoint[3];
+		hlGetDoublev(HL_PROXY_POSITION,leanPoint);
+		leanPoint[2] = 1;
+		hduVector3Dd winCoordPoint;				
+		toScreen(hduVector3Dd(leanPoint),winCoordPoint);
+		
+		try{
+			if(collectionsManager->isNode(draggedElementID)){
+				CollectionsManager::NodeData & nd = collectionsManager->getNodeDataFromID(draggedElementID);
+				movedNode = true;
+				executeCommand('m',nd.diagramId,winCoordPoint[0],collectionsManager->getScreenHeight() - winCoordPoint[1],0,0);
+			}else if(collectionsManager->isEdge(draggedElementID)){ // double check necessary for the user might have changed the tab or closed the window before releasing the spring 		
+				springStart[2] = 1;
+				hduVector3Dd startWinCoordPoint;
+				toScreen(hduVector3Dd(springStart),startWinCoordPoint);//it's an edge, we also need the point where the motion has started
+				CollectionsManager::EdgeData & ed = collectionsManager->getEdgeDataFromID(draggedElementID);
+				executeCommand('m', ed.diagramId, 
+					winCoordPoint[0],
+					collectionsManager->getScreenHeight() - winCoordPoint[1],
+					startWinCoordPoint[0],
+					collectionsManager->getScreenHeight() - startWinCoordPoint[1]
+				);
+			}
+		}catch(int){
+			stopExecution("Button 1 callback: Could not retrieve element from ID");
+		}
+		/* with second click we place the element and it becomes the last moved element */
+		lastMovedElement = draggedElementID;
+		secondClick = false;
+	}
+}
+
+void HapticManager::setAttractTo(jint hapticId){
+	if(attractionStatus == NO_ATTRACTION) // starts only if there is no previous attraction being overwritten
+		attractionStatus = START_ATTRACTION;	
+	attractToHapticId = hapticId;
+	alreadyTouchingAttractingElement = false;
+}
+
+void HapticManager::pickUp(jint hapticId){
+	springStatus = START_DRAGGING;
+	secondClick = true;
+	hlGetDoublev(HL_PROXY_POSITION,springStart);
+}
+
+bool HapticManager::wasAlreadyTouchingAttractingElement(){
+	return alreadyTouchingAttractingElement;
+}
+
+void HapticManager::draw(void){
+	// Start haptic frame.  (Must do this before rendering any haptic shapes.)
+    hlBeginFrame();   
+
+	if(!doneOnce){
+		hlStartEffect(HL_EFFECT_FRICTION, frictionFX);
+		doneOnce = true;
+	}
+
+	if(wall){
+		// Plane shape.
+		hlPushAttrib(HL_MATERIAL_BIT|HL_TOUCH_BIT);
+		hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, wall1Id);
+		hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1);
+		hlMaterialf(HL_FRONT, HL_POPTHROUGH, 0 );
+		hlMaterialf(HL_FRONT, HL_STATIC_FRICTION, 0);
+		hlMaterialf(HL_FRONT, HL_DYNAMIC_FRICTION, 0);
+		hlTouchModel(HL_CONTACT);
+		hlTouchableFace(HL_FRONT);
+		drawPlane(-0.005);
+		hlEndShape();
+#define SECOND_PLANE
+#ifdef SECOND_PLANE
+		hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, wall2Id);
+		hlMaterialf(HL_BACK, HL_STIFFNESS, 1);
+		hlTouchableFace(HL_BACK);
+		hlMaterialf(HL_FRONT, HL_POPTHROUGH, 0 );
+		hlMaterialf(HL_FRONT, HL_STATIC_FRICTION, 0);
+		hlMaterialf(HL_FRONT, HL_DYNAMIC_FRICTION, 0);
+		drawPlane(0.005);
+		hlEndShape();
+#endif
+		hlPopAttrib();
+	}
+
+    // Start a new haptic shape.  Use the feedback buffer to capture OpenGL 
+    // geometry for haptic rendering.
+
+	// draw nodes
+	int size = collectionsManager->getNodesNum();
+	
+	for(int i=0; i< size;i++){
+		CollectionsManager::NodeData & nd = collectionsManager->getNodeData(i);
+
+		hlPushAttrib(HL_HINT_BIT);
+		hlHinti(HL_SHAPE_FEEDBACK_BUFFER_VERTICES, 3);
+		hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, nd.hapticId);
+		 hlTouchModel(HL_CONSTRAINT);
+		 hlTouchModelf(HL_SNAP_DISTANCE ,  4.0f );
+		 hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1.0);
+		 hlMaterialf(HL_FRONT_AND_BACK, HL_DAMPING, 0);
+
+		 hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, 0);
+		 hlMaterialf(HL_FRONT_AND_BACK, HL_DYNAMIC_FRICTION, 0);
+
+		 //draw the point
+		 glBegin(GL_POINTS);
+		  glVertex3d(nd.x,nd.y,0);
+		 glEnd();
+		 
+		// End the shape.
+		hlEndShape();
+		hlPopAttrib();
+
+		// saves the coorinates for a later use so that for the effect we don't need to cycle again
+		if(attractionStatus == START_ATTRACTION || attractionStatus == DOING_ATTRACTION){
+			if(nd.hapticId == attractToHapticId){
+				attractToCoord[0] = nd.x;
+				attractToCoord[1] = nd.y;
+				attractToCoord[2] = 0;
+			}
+		}
+	}
+
+	/* draw lines. When a node is moved, edges are not drawn for the very first haptic frame after the shift.   *
+	 *  This is to address the behaviour of the device which, after a n ode is moved cannot see the styilus     *
+	 *  touching the edge. As a consequence of that in hlMotionCB() when moving away from a node along an edge  *
+	 *  no edge name will be spoken and furthermore the pick up botton won't work as the device thinks it's not *  
+	 *  touching anything. Not drawing the edge on the first frame, seems to address this issue.                */
+	if(!movedNode) {
+		size = collectionsManager->getEdgesNum();
+		for(int i = 0; i<size; i++){
+			CollectionsManager::EdgeData & ed = collectionsManager->getEdgeData(i);
+			hlPushAttrib(HL_HINT_BIT|HL_MATERIAL_BIT);
+			hlHinti(HL_SHAPE_FEEDBACK_BUFFER_VERTICES, ed.getSize());
+
+			hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, ed.hapticId);
+			hlTouchModel(HL_CONSTRAINT);
+			hlTouchModelf(HL_SNAP_DISTANCE, ( (stickyMode && springStatus != DRAGGING && springStatus != START_DRAGGING)  ? 20.0f : 3.5f ));
+			hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1);
+			
+			if(ed.stipplePattern == 0xFFFF){
+				hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, 0.5f);
+			}
+
+			glLineWidth(1.0);
+			
+			glBegin(GL_LINES);
+			 for(unsigned int j = 0; j < ed.getSize(); j++){
+				for(unsigned int k = j; k < ed.getSize(); k++){
+					if(ed.adjMatrix[j][k]){
+						if(  k >= ed.nodeStart && 
+							   (!stickyMode || (springStatus != RELEASED && springStatus != STOP_DRAGGING))
+							   && attractionStatus != DOING_ATTRACTION && attractionStatus != START_ATTRACTION){ // not a line connecting two edges and stickyMode not enabled
+							/* it's drawing a line from a point to a node, thus it draws it a little shorter*/
+							double hypotenuse = sqrt( (ed.x[j] - ed.x[k])*(ed.x[j] - ed.x[k]) + (ed.y[j] - ed.y[k])*(ed.y[j] - ed.y[k]));		
+							if(hypotenuse == 0)
+								continue;
+							double mySin = (ed.y[j] - ed.y[k])/hypotenuse;
+							double myCos = (ed.x[j] - ed.x[k])/hypotenuse; 
+							glVertex3d(ed.x[k]+(EDGE_SHORTEND_VAL * myCos) , ed.y[k]+(EDGE_SHORTEND_VAL*mySin),0);
+							if(j >= ed.nodeStart) //its a direct edge connecting two nodes 
+								glVertex3d(ed.x[j]+(EDGE_SHORTEND_VAL * -myCos),ed.y[j]+(EDGE_SHORTEND_VAL*-mySin),0);
+							else
+								glVertex3d(ed.x[j],ed.y[j],0);
+						}else{
+							glVertex3d(ed.x[j],ed.y[j],0);
+							glVertex3d(ed.x[k],ed.y[k],0);
+						}
+					}
+				}
+			}
+			glEnd();
+			hlEndShape();
+			hlPopAttrib();
+
+			// saves the coorinates for a later use so that for the effect we don't need to cycle again
+			if(attractionStatus == START_ATTRACTION || attractionStatus == DOING_ATTRACTION){
+				if(ed.hapticId == attractToHapticId){
+					attractToCoord[0] = ed.attractPoint[0];
+					attractToCoord[1] = ed.attractPoint[1];
+					attractToCoord[2] = 0;
+				}
+			}
+		}
+	}else {
+		movedNode = false;
+	}
+	/* ---  FORCES --- */
+	/* friction */
+	if(lastTouchedEdgeID != HL_OBJECT_ANY){
+		/* check if the edge is still there, it might not as the tab could be changed */
+		if(collectionsManager->isEdge(lastTouchedEdgeID)){
+			//hlGetShapeBooleanv(lastTouchedEdgeID,HL_PROXY_IS_TOUCHING,&isTouching); FIXME to remove
+			if(frictionDisabled||lastTouchedEdgeIsUntouched/*(isTouching == HL_FALSE)*/){
+					hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0);
+					hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0);
+				}else{
+					if(lastTouchedEdgeStipplePattern == 0xAAAA){ // dotted
+						hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.4f);
+						hlEffectd(HL_EFFECT_PROPERTY_GAIN, 1);
+					}else if(lastTouchedEdgeStipplePattern == 0xF0F0){ // dashed
+						hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.6);
+						hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0.6);
+					}
+			}
+		}else{
+			lastTouchedEdgeID = HL_OBJECT_ANY;
+			hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0);
+			hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0);
+		}
+		hlUpdateEffect(frictionFX); 
+	}
+
+	// spring 
+	if(springStatus == START_DRAGGING){
+		hlPushAttrib(HL_EFFECT_BIT);
+		hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0.3);
+		hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.5);
+		
+		hduMatrix worldToView;
+		hduMatrix graphicToTouch;
+		hduMatrix touchToWorkspace;
+		hduMatrix WorldToDevice;
+		glMatrixMode(GL_MODELVIEW);
+		glGetDoublev(GL_MODELVIEW_MATRIX,worldToView);
+		hlGetDoublev(HL_VIEWTOUCH_MATRIX, graphicToTouch );
+		hlGetDoublev(HL_TOUCHWORKSPACE_MATRIX, touchToWorkspace );
+		WorldToDevice = worldToView * graphicToTouch * touchToWorkspace;
+
+		hduVector3Dd dst;
+		WorldToDevice.multVecMatrix(hduVector3Dd(springStart[0],springStart[1],0),dst);
+		
+		hlEffectdv(HL_EFFECT_PROPERTY_POSITION, dst);
+		hlStartEffect(HL_EFFECT_SPRING, springFX);
+		hlPopAttrib();
+		springStatus = DRAGGING;
+	}else if(springStatus == STOP_DRAGGING){
+		hlStopEffect(springFX);
+		springStatus = RELEASED;
+	}
+	
+	// attraction 
+	HLboolean isTouching = HL_FALSE;
+	if(attractionStatus == DOING_ATTRACTION || attractionStatus == START_ATTRACTION){
+		/* we need to be sure we're currently touching the element */
+		if(attractToHapticId != HL_OBJECT_ANY){
+			hlGetShapeBooleanv(attractToHapticId,HL_PROXY_IS_TOUCHING,&isTouching);
+		}
+		
+		// when touching the element we're getting attracted to, then shut the force down
+		if(isTouching == HL_TRUE){ 
+			if(attractionStatus == DOING_ATTRACTION){ // we finally reached the attracting node
+				attractionStatus = STOP_ATTRACTION;
+			}else{
+				attractionStatus = NO_ATTRACTION; // we were already touching the node we wanted to get attracted to 
+				alreadyTouchingAttractingElement = true;
+			}
+		}
+	}
+
+	if(attractionStatus == STOP_ATTRACTION){
+		attractionStatus = NO_ATTRACTION;
+	    hlStopEffect(attractionFX);
+	} else if(attractionStatus == START_ATTRACTION || attractionStatus == DOING_ATTRACTION){
+		HLdouble devicePosition[3];
+		HDdouble direction[3];
+		hlGetDoublev(HL_DEVICE_POSITION,devicePosition);
+		direction[0] = attractToCoord[0] - devicePosition[0];
+		direction[1] = attractToCoord[1] - devicePosition[1];
+		direction[2] = attractToCoord[2] - devicePosition[2];
+		HDdouble directionNorm = norm(direction);
+		if(directionNorm != 0 ){
+			direction[0] /= directionNorm;
+			direction[1] /= directionNorm;
+			direction[2] /= directionNorm;
+		}
+		hlPushAttrib(HL_EFFECT_BIT);
+		hlEffectdv(HL_EFFECT_PROPERTY_DIRECTION, direction);
+		hlEffectd(HL_EFFECT_PROPERTY_GAIN, 0.5f);
+		hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, 0.5f);
+		if(attractionStatus == START_ATTRACTION){
+			hlStartEffect(HL_EFFECT_CONSTANT, attractionFX);
+			attractionStatus = DOING_ATTRACTION;
+		}else{
+			hlUpdateEffect(attractionFX);
+		}
+		hlPopAttrib();
+	}
+#ifdef VIBRATION
+	if(vibrationStatus == START_VIBRATION){
+		hlStartEffect(HL_EFFECT_CALLBACK, vibrationFX);
+		vibrationEnd = clock() + VIBRATION_DURATION;
+		vibrationStatus = DOING_VIBRATION;
+	}else if(vibrationStatus == DOING_VIBRATION){
+		if(clock() >= vibrationEnd){
+			hlStopEffect(vibrationFX);
+			vibrationStatus = NO_VIBRATION;
+		}
+	}
+#endif
+    // End the haptic frame.
+    hlEndFrame();
+}
+
+void HapticManager::drawPlane(float zoffset){
+	GLuint displayList = (zoffset < 0) ? displayList1 : displayList2;
+
+	if (displayList){
+		glCallList(displayList);
+	}
+	else{
+		if(zoffset < 0){
+			displayList1 = glGenLists(1);
+			glNewList(displayList1, GL_COMPILE_AND_EXECUTE);
+		}else{
+			displayList2 = glGenLists(1);
+			glNewList(displayList2, GL_COMPILE_AND_EXECUTE);
+		}	
+		glNewList(displayList, GL_COMPILE_AND_EXECUTE);
+		glPushAttrib(GL_ENABLE_BIT);
+		glPolygonOffset(1,1);
+		glEnable(GL_POLYGON_OFFSET_FILL);
+		
+		glBegin(GL_QUADS);
+		glVertex3f(-5, -5, zoffset);
+		glVertex3f(5, -5, zoffset);
+		glVertex3f(5, 5, zoffset);
+		glVertex3f(-5, 5, zoffset);
+		glEnd();
+		glDisable(GL_POLYGON_OFFSET_FILL);
+		glPopAttrib();
+		glEndList();
+	}
+	
+}
+
+HHD HapticManager::ghHD = HD_INVALID_HANDLE;
+HHLRC HapticManager::ghHLRC = 0;
+const double HapticManager::EDGE_SHORTEND_VAL = 0.06;
+const unsigned int HapticManager::CLICK_INTERVAL = CLOCKS_PER_SEC/5;
+const double HapticManager::EDGE_SPEECH_DISTANCE_WHEN_LEAVING_NODE = 0.03;
+#ifdef VIBRATION
+const HDdouble HapticManager::VIBRATION_AMPLITUDE = 0.88;//0.88;
+const HDint HapticManager::VIBRATION_FREQUENCY = 170;//160;
+const unsigned int HapticManager::VIBRATION_DURATION = CLOCKS_PER_SEC/10; // = 100 ms
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/HapticManager.h	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,122 @@
+#pragma once
+#include "stdAfx.h"
+#include <time.h>
+#include "HapticException.h"
+#include "CollectionsManager.h"
+
+#define NOVIBRATION
+
+/* The haptic manager draws the diagram openGL scene haptically.    *
+ * In order to get all the data about the diagram it's constructed  *
+ * passing a reference to the CollectionManager                     */
+class HapticManager
+{
+	enum AttractionStatus  {START_ATTRACTION, DOING_ATTRACTION, NO_ATTRACTION, STOP_ATTRACTION};
+	enum SpringStatus { DRAGGING, RELEASED, START_DRAGGING, STOP_DRAGGING };
+	enum VibrationStatus { START_VIBRATION , DOING_VIBRATION , NO_VIBRATION, CAN_VIBRATE};
+	
+
+	/* Haptic device and rendering context handles. */
+	HLuint wall1Id;
+	HLuint wall2Id;
+	HLuint frictionFX;
+	HLuint vibrationFX;
+	HLuint attractionFX;
+	HLuint springFX;
+	VibrationStatus vibrationStatus;
+	AttractionStatus attractionStatus; 
+	SpringStatus springStatus;
+#ifdef VIBRATION
+	static const HDdouble VIBRATION_AMPLITUDE;
+	static const HDint VIBRATION_FREQUENCY;
+	static const unsigned int VIBRATION_DURATION;
+#endif	
+	static const double EDGE_SHORTEND_VAL;
+	static const double EDGE_SPEECH_DISTANCE_WHEN_LEAVING_NODE;
+	static const unsigned int CLICK_INTERVAL;
+	clock_t clickStart;
+	clock_t vibrationEnd;
+	HDdouble forceBeforeVibration[3];
+	CollectionsManager *collectionsManager;
+	bool wall;
+	jint attractToHapticId;
+	bool alreadyTouchingAttractingElement;
+	jdouble attractToCoord[3];
+	// this is needed to calulate the distance between the proxy and the last touched node
+	HLdouble lastTouchedNodePosition[3];
+	HLint lastTouchedNodeID;
+	HLint lastTouchedEdgeID;
+	jint lastTouchedEdgeStipplePattern;
+	HLint lastHighlightElementID;
+	HLint draggedElementID;
+	/* when dropping a node after moving it, unless one detouches and touches again the node, 
+		the hlGetShapeBooleanv(HL_PROXY_IS_TOUCHING) routine will return a false value when
+		one tries to hook the node again. Even tough the touch and untouch callback are called
+		correctly. this variable keeps track of the last moved element, setting it to HL_OBJECT_ANY
+		on the detouch callback so that this "buggy" behaviour is worked around
+	*/
+	HLuint lastMovedElement;
+	bool stillTouchingLastMovedElement;
+	/*  this variable is to workaround the HL_PROXY_IS_TOUCHNIG problem to fix issue of stipple pattern:
+		when dropping a node after having picked it up, then the hlGetShapeBooleanv may return false 
+		for all the edges attached to a moved node or for a moved edge. Because of that the stipple 
+		pattern for dotted and dashed lines might not be enabled. The check is however necessary to stop the 
+		effect when actually untouching the node or when changing tab after the pick up and drop.
+		The variable is set/unset in the touching callbacks which works fine.
+	*/
+	bool lastTouchedEdgeIsUntouched;
+	HLdouble springStart[3];
+	GLuint displayList1;
+	GLuint displayList2;
+	bool frictionDisabled;
+	bool doneOnce;
+	bool secondClick;
+	bool stickyMode;
+	bool doubleClick;
+	void drawPlane(float offset);
+	void (*executeCommand)(const jchar cmd, const jint ID, const jdouble startx, const jdouble starty, const jdouble endx, const jdouble endy);
+
+	static void HLCALLBACK hlButton1CB(HLenum event, HLuint object, HLenum thread,
+                                     HLcache *cache, void *userdata);
+	static void HLCALLBACK hlButton2CB(HLenum event, HLuint object, HLenum thread,
+                                     HLcache *cache, void *userdata);
+	static void HLCALLBACK hlTouchCB(HLenum event, HLuint object, HLenum thread,
+                                     HLcache *cache, void *userdata);
+	static void HLCALLBACK hlUntouchCB(HLenum event, HLuint object, HLenum thread,
+                                     HLcache *cache, void *userdata);
+	static void HLCALLBACK hlMotionCB(HLenum event, HLuint object, HLenum thread,
+										   HLcache *cache, void *userdata);
+#ifdef VIBRATION
+	static void HLCALLBACK vibrationCB(HDdouble force[3], HLcache *cache, void *userdata);
+
+	static void HLCALLBACK beforeVibrationCB(HLcache *cache, void *userdata);
+
+	static void HLCALLBACK afterVibrationCB(HLcache *cache, void *userdata);
+#endif
+
+public:
+
+	enum ClickStatus { NO_CLICK, ONE_CLICK, TWO_CLICK };
+
+	static HHD ghHD;
+	static HHLRC ghHLRC;
+	HapticManager(CollectionsManager * cManager, void (*func)(const jchar cmd, const jint ID, const jdouble startx, const jdouble starty, const jdouble endx, const jdouble endy)) 
+		: executeCommand(func), wall(true), collectionsManager(cManager), vibrationStatus(NO_VIBRATION), springStatus(RELEASED), alreadyTouchingAttractingElement(false),
+		  doneOnce(false), attractionStatus(NO_ATTRACTION), frictionDisabled(false), secondClick(false), clickStart(0), stickyMode(true),
+			lastMovedElement(HL_OBJECT_ANY),stillTouchingLastMovedElement(false),lastTouchedEdgeIsUntouched(false){};
+	~HapticManager(void) {}
+	void init(void) throw(HapticException);
+	void draw(void);
+	void setAttractTo(jint hapticId);
+	void pickUp(jint hapticId);
+	bool wasAlreadyTouchingAttractingElement(void);
+
+	void enableWall(void){ wall = true; }
+	void disableWall(void){ wall = false; }
+
+	bool isDraggingNode() const { return springStatus == DRAGGING && collectionsManager->isNode(draggedElementID); }
+	bool isDraggingEdge() const { return springStatus == DRAGGING && collectionsManager->isEdge(draggedElementID); }
+	ClickStatus getButton1Status();
+	void doButton1Click(bool doubleClick);
+	
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/Haptics.vcproj	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,504 @@
+<?xml version="1.0" encoding="gb2312"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="9.00"
+	Name="Haptics"
+	ProjectGUID="{3FAB66F5-BA54-470F-9D4B-3E73E58A76BC}"
+	RootNamespace="Haptics"
+	TargetFrameworkVersion="131072"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+		<Platform
+			Name="x64"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory=".\Release"
+			ConfigurationType="2"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				TypeLibraryName=".\Release/HelloSphere.tlb"
+				HeaderFileName=""
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				InlineFunctionExpansion="1"
+				AdditionalIncludeDirectories="&quot;C:\Program Files\Java\jdk1.6.0_25\include&quot;;&quot;C:\Program Files\Java\jdk1.6.0_25\include\win32&quot;;include;&quot;$(3DTOUCH_BASE)\include&quot;;&quot;$(3DTOUCH_BASE)\utilities\include&quot;"
+				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+				StringPooling="true"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				PrecompiledHeaderFile=".\Release/HelloSphere.pch"
+				AssemblerListingLocation=".\Release/"
+				ObjectFile=".\Release/"
+				ProgramDataBaseFileName=".\Release/"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="NDEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="hl.lib hlu.lib hd.lib hdu.lib glut32.lib"
+				LinkIncremental="1"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\utilities\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\&quot;"
+				ProgramDatabaseFile=".\Release/HelloSphere.pdb"
+				SubSystem="1"
+				RandomizedBaseAddress="1"
+				DataExecutionPrevention="0"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory=".\Debug"
+			IntermediateDirectory=".\Debug"
+			ConfigurationType="2"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				TypeLibraryName=".\Debug/HelloSphere.tlb"
+				HeaderFileName=""
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="&quot;C:\Program Files\Java\jdk1.6.0_24\include\win32&quot;;&quot;C:\Program Files\Java\jdk1.6.0_24\include&quot;;include;&quot;$(3DTOUCH_BASE)\include&quot;;&quot;$(3DTOUCH_BASE)\utilities\include&quot;"
+				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				PrecompiledHeaderFile=".\Debug/HelloSphere.pch"
+				AssemblerListingLocation=".\Debug/"
+				ObjectFile=".\Debug/"
+				ProgramDataBaseFileName=".\Debug/"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				DebugInformationFormat="4"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="_DEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="hl.lib hlu.lib hd.lib hdu.lib glut32.lib"
+				OutputFile=".\Debug/HelloSphere.exe"
+				LinkIncremental="1"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\utilities\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\&quot;"
+				GenerateDebugInformation="true"
+				ProgramDatabaseFile=".\Debug/HelloSphere.pdb"
+				SubSystem="1"
+				RandomizedBaseAddress="1"
+				DataExecutionPrevention="0"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|x64"
+			OutputDirectory="$(PlatformName)\$(ConfigurationName)"
+			IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
+			ConfigurationType="1"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				TargetEnvironment="3"
+				TypeLibraryName=".\Release/HelloSphere.tlb"
+				HeaderFileName=""
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				InlineFunctionExpansion="1"
+				AdditionalIncludeDirectories="include,$(3DTOUCH_BASE)\include,$(3DTOUCH_BASE)\utilities\include"
+				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+				StringPooling="true"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				PrecompiledHeaderFile=".\Release/HelloSphere.pch"
+				AssemblerListingLocation=".\Release/"
+				ObjectFile=".\Release/"
+				ProgramDataBaseFileName=".\Release/"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="NDEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="hl.lib hlu.lib hd.lib hdu.lib glut32.lib"
+				OutputFile="$(OutDir)\$(ProjectName).exe"
+				LinkIncremental="1"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\utilities\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\&quot;"
+				ProgramDatabaseFile=".\Release/HelloSphere.pdb"
+				SubSystem="1"
+				TargetMachine="17"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Debug|x64"
+			OutputDirectory="$(PlatformName)\$(ConfigurationName)"
+			IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
+			ConfigurationType="1"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				TargetEnvironment="3"
+				TypeLibraryName=".\Debug/HelloSphere.tlb"
+				HeaderFileName=""
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="include,$(3DTOUCH_BASE)\include,$(3DTOUCH_BASE)\utilities\include"
+				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				PrecompiledHeaderFile=".\Debug/HelloSphere.pch"
+				AssemblerListingLocation=".\Debug/"
+				ObjectFile=".\Debug/"
+				ProgramDataBaseFileName=".\Debug/"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				DebugInformationFormat="3"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="_DEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="hl.lib hlu.lib hd.lib hdu.lib glut32.lib"
+				OutputFile="$(OutDir)\$(ProjectName).exe"
+				LinkIncremental="1"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\utilities\lib\$(PlatformName)\$(ConfigurationName)&quot;;&quot;$(3DTOUCH_BASE)\lib\$(PlatformName)\&quot;"
+				GenerateDebugInformation="true"
+				ProgramDatabaseFile=".\Debug/HelloSphere.pdb"
+				SubSystem="1"
+				TargetMachine="17"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+			>
+			<File
+				RelativePath=".\CollectionsManager.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\GraphicManager.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\HapticException.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\HapticManager.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\stdafx.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_Haptics.cpp"
+				>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						Optimization="2"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						Optimization="0"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+						BasicRuntimeChecks="3"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						Optimization="2"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						Optimization="0"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+						BasicRuntimeChecks="3"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath=".\utils.cpp"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Header Files"
+			Filter="h;hpp;hxx;hm;inl"
+			>
+			<File
+				RelativePath=".\CollectionsManager.h"
+				>
+			</File>
+			<File
+				RelativePath=".\GraphicManager.h"
+				>
+			</File>
+			<File
+				RelativePath=".\HapticException.h"
+				>
+			</File>
+			<File
+				RelativePath=".\HapticManager.h"
+				>
+			</File>
+			<File
+				RelativePath=".\stdafx.h"
+				>
+			</File>
+			<File
+				RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_Haptics.h"
+				>
+			</File>
+			<File
+				RelativePath=".\utils.h"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Resource Files"
+			Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+			>
+		</Filter>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/stdafx.cpp	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,2 @@
+
+#include "stdafx.h"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/stdafx.h	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,20 @@
+#ifndef stdAfx_H_
+#define stdAfx_H_
+
+#if defined(WIN32)
+#include <windows.h>
+#endif
+
+#if defined(WIN32) || defined(linux)
+#include <GL/glut.h>
+#elif defined(__APPLE__)
+#include <GLUT/glut.h>
+#endif
+
+#include <HL/hl.h>
+#include <HDU/hduMatrix.h>
+#include <HDU/hduError.h>
+
+#include <HLU/hlu.h>
+
+#endif //stdAfx_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_Haptics.cpp	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,518 @@
+#include "uk_ac_qmul_eecs_ccmi_haptics_Haptics.h"
+#include "stdafx.h"
+#include "GraphicManager.h"
+#include "HapticManager.h"
+#include "HapticException.h"
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include <setjmp.h>
+#include "utils.h"
+
+#define CURSOR_SIZE_PIXELS 20
+
+enum {JMP_OK =0, JMP_EXIT };
+
+/**************************
+ Function prototypes. 
+ ***************************/
+/* callbacks */
+void displayCallback(void);
+void reshapeCallback(int width, int height);
+void idleCallback(void);   
+void exitProcedure(void);
+void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y,const jdouble startX, const jdouble startY);
+
+void drawCursor();
+void updateWorkspace();
+
+void initJniVariables(void);
+/*************************
+ global variables 
+ **************************/
+GraphicManager *gManager;
+HapticManager *hManager;
+CollectionsManager *cManager;
+JNIEnv *env;
+jobject *lock;
+int width;
+int height;
+jmp_buf jmpenv;	
+int jmpval = 0;
+/* jni variables */
+jclass hapticClass;
+jclass hapticListenerClass;
+jfieldID shutdownfieldId;
+jfieldID newHapticIdfieldId;
+jfieldID dumpHapticIdfieldId;
+jfieldID currentHapticIdfieldId;
+jfieldID hapticInitFailedfieldId;
+jfieldID attractTofieldId;
+jfieldID attractToHapticIdFieldId;
+jfieldID pickUpfieldId;
+jfieldID pickUpHapticIdFieldId;
+jfieldID hapticListenerFieldId;
+jfieldID cmdFieldId; // belongs to the haptic listener
+jfieldID diagramElementFieldId; // belongs to the haptic listener
+jfieldID xFieldId; // belongs to the haptic listener
+jfieldID yFieldId; // belongs to the haptic listener
+jfieldID startXFieldId; // belongs to the haptic listener
+jfieldID startYFieldId; // belongs to the haptic listener
+jobject hapticListener;
+jmethodID notifyMethodId;
+jmethodID notifyListenerMethodId;
+jmethodID waitMethodId;
+
+/*******************************************************************************
+ Initializes GLUT for displaying a simple haptic scene.
+*******************************************************************************/
+JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_init
+  (JNIEnv *environment, jobject obj, jint w, jint h){
+	env = environment;
+	lock = &obj;
+	/* fake main argv and argc as this is a dll */
+	char *argv[1] = {"OmniHaptics"}; 
+	int argc = 1;
+
+	initJniVariables();
+
+	/* glut initialization */
+    glutInit(&argc, argv);
+    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
+    glutInitWindowSize(w, h);
+    glutCreateWindow("CCmI Diagram Haptics");
+
+    /* glut callbacks */
+    glutDisplayFunc(displayCallback);
+    glutReshapeFunc(reshapeCallback);
+    glutIdleFunc(idleCallback);
+	
+	cManager = new CollectionsManager(env,lock);
+	hManager = new HapticManager(cManager,hapticCommandCB);
+	gManager = new GraphicManager(cManager,hManager);
+	cManager->init();
+	gManager->init();    
+		
+	// try to initialize the haptic, tell the java thread about the success/failure. The endeavour
+	// takes the lock on the haptics java object in order to access the nodes and edges concurrently 
+	if(env->MonitorEnter(*lock) != JNI_OK){
+		stopExecution("Could not allocate memory for haptic thread monitor");
+	}
+	checkExceptions(env, "Could not enter monitor on Haptics");
+
+	bool mustSayGoodbye = false;
+	try{ 
+		hManager->init();
+	}catch (HapticException e){
+		env->SetBooleanField(*lock,hapticInitFailedfieldId,JNI_TRUE);
+		mustSayGoodbye = true;
+	}
+	
+	if(mustSayGoodbye)
+		std::cout << "Failed to initialize haptic device" << std::endl;
+	else
+		std::cout << "Haptic device successfully initialized" << std::endl;
+
+	// notify the other thread
+	env->CallVoidMethod(*lock,notifyMethodId);
+	checkExceptions(env,"Could not call notify() on Haptics");
+	
+	//release the lock
+	if(env->MonitorExit(*lock) != JNI_OK){
+		std::cerr << "Could not release memory for haptic thread monitor" << std::endl;
+		exit(-1);
+	}
+	
+	if(mustSayGoodbye)
+		/* initialization failed: return */
+		return -1;
+
+	/* use setjmp to be able to jump off the glutMainLoop when the user shuts the program down */
+	jmpval = setjmp(jmpenv);
+
+	/* star the loop*/
+	if(jmpval == JMP_OK){
+		glutMainLoop();
+	}else{
+		exitProcedure();
+	}
+	return 0;
+}
+
+/*******************************************************************************
+ GLUT callback for redrawing the view.
+*******************************************************************************/
+void displayCallback(){ 
+
+	// takes the lock on the haptics java obejct in order to access the nodes and edges concurrently 
+	if(env->MonitorEnter(*lock) != JNI_OK){
+		stopExecution("Could not allocate memory for haptic thread monitor");
+	}
+	checkExceptions(env,"Could not enter monitor on Haptics");
+
+	// check if there is a shutdown request 
+	if( env->GetBooleanField(*lock,shutdownfieldId) == JNI_TRUE ){
+		// notify the other thread that this thread is about to die
+		env->CallVoidMethod(*lock,notifyMethodId);
+		checkExceptions(env, "Could not call notify() on Haptics");
+		// release the lock 
+		if(env->MonitorExit(*lock) != JNI_OK){
+			std::cerr << "Could not release memory for haptic thread monitor" << std::endl;
+		}
+		longjmp(jmpenv,JMP_EXIT);
+	}
+
+	// check if the user asked to be attracted to a node
+	jboolean attractTo = env->GetBooleanField(*lock,attractTofieldId);
+	jint attractToDiagramId = 0;
+	if(attractTo == JNI_TRUE){
+		env->SetBooleanField(*lock,attractTofieldId,JNI_FALSE);
+		jint attractToHapticId = env->GetIntField(*lock,attractToHapticIdFieldId);
+		if(cManager->isNode(attractToHapticId)){
+			attractToDiagramId = cManager->getNodeDataFromID(attractToHapticId).diagramId;
+		}else{
+			attractToDiagramId = cManager->getEdgeDataFromID(attractToHapticId).diagramId;
+		}
+		hManager->setAttractTo(attractToHapticId);
+	} 
+	// check if the user picked up a node
+	jboolean pickUp = env->GetBooleanField(*lock,pickUpfieldId);
+	jint pickUpDiagramId = 0;
+	if(pickUp == JNI_TRUE){
+		env->SetBooleanField(*lock,pickUpfieldId,JNI_FALSE);
+		jint pickUpHapticId = env->GetIntField(*lock,pickUpHapticIdFieldId);
+		if(cManager->isNode(pickUpHapticId)){
+			pickUpDiagramId = cManager->getNodeDataFromID(pickUpHapticId).diagramId;
+		}else{
+			pickUpDiagramId = cManager->getEdgeDataFromID(pickUpHapticId).diagramId;
+		}
+		hManager->pickUp(pickUpDiagramId);
+	}
+
+	// draw the scene graphically and haptically 
+	hManager->draw(); 
+	gManager->draw();
+
+	// check whether the java thread needs to either create or dump an haptic id 
+	jboolean needsNewHapticId = env->GetBooleanField(*lock,newHapticIdfieldId);
+	if(needsNewHapticId == JNI_TRUE){	
+		// set int currentHapticId of class Haptics with a new generated id
+		jint newHapticid = hlGenShapes(1);
+		env->SetIntField(*lock, currentHapticIdfieldId, newHapticid);
+		//set the boolean field to false as the other thread now has an id
+		env->SetBooleanField(*lock, newHapticIdfieldId, JNI_FALSE);
+		//notify the other thread	
+		env->CallVoidMethod(*lock,notifyMethodId);
+		checkExceptions(env, "Could not call notify() on Haptics");
+	}
+	jboolean needsDumpOldHapticId = env->GetBooleanField(*lock, dumpHapticIdfieldId);
+	if(needsDumpOldHapticId == JNI_TRUE){
+		// get the id of the deleted element from the other thread
+		jint oldHapticId = env->GetIntField(*lock,currentHapticIdfieldId);
+		// free the old haptic id 
+		hlDeleteShapes(oldHapticId,1);
+		// set the boolean field as the id has been cleaned up
+		env->SetBooleanField(*lock,dumpHapticIdfieldId,JNI_FALSE);
+		// notify the other thread
+		env->CallVoidMethod(*lock,notifyMethodId);
+		checkExceptions(env, "Could not call notify() on Haptics");
+	}
+	
+	/* release lock */
+	if(env->MonitorExit(*lock) != JNI_OK){
+		stopExecution("Could not release memory for haptic thread monitor");
+	}
+
+	/* it's important that this call be outside the monitors, else a deadlock occurs */
+	// Call any event callbacks that have been triggered.
+	if(attractTo == JNI_TRUE){
+		if(hManager->wasAlreadyTouchingAttractingElement()){
+			hapticCommandCB('t',attractToDiagramId,0,0,0,0);
+		}
+	}
+
+	hlCheckEvents();
+	HapticManager::ClickStatus click = hManager->getButton1Status();
+	if(click == HapticManager::ONE_CLICK){
+		hManager->doButton1Click(false);
+	}else if(click == HapticManager::TWO_CLICK){
+		hManager->doButton1Click(true);
+	}
+
+    glutSwapBuffers();
+}
+/*******************************************************************************
+ GLUT callback for reshaping the window.  This is the main place where the 
+ viewing and workspace transforms get initialized.
+*******************************************************************************/
+void reshapeCallback(int w, int h){
+    static const double kPI = 3.1415926535897932384626433832795;
+    static const double kFovY = 40;
+
+    static const double nearDist = 1.0 / tan((kFovY / 2.0) * kPI / 180.0 /* radiants for 1 degree */);
+	static const double farDist = nearDist + 2.0;
+	double aspect;
+	width = w;
+	height = h;
+
+	cManager->setScreenHeight(h);
+    glViewport(0, 0, width, height);
+    // Compute the viewing parameters based on a fixed fov and viewing
+    // a canonical box centered at the origin.
+    aspect = (double)  width/height ;
+
+    glMatrixMode(GL_PROJECTION);
+    glLoadIdentity();
+    gluPerspective(kFovY, aspect,nearDist, farDist);
+	// Place the camera down the Z axis looking at the origin.
+	glMatrixMode(GL_MODELVIEW);
+     glLoadIdentity();            
+    gluLookAt(0, 0, nearDist + 1.0,
+              0, 0, 0,
+             0, 1, 0);
+
+	hduVector3Dd origin;
+	fromScreen(hduVector3Dd(0, 0, 0),origin);
+	glLoadIdentity(); 
+	gluLookAt(-origin[0],origin[1], nearDist + 1.0,
+              -origin[0],origin[1], 0,
+               0, 1, 0);
+
+    hduVector3Dd end;
+	fromScreen(hduVector3Dd(w, h, 0),end);
+	
+	//glTranslatef(end[0]-origin[0],-(end[1]-origin[1]),0);
+	
+	updateWorkspace();
+}
+
+
+/*******************************************************************************
+ Use the current OpenGL viewing transforms to initialize a transform for the
+ haptic device workspace so that it's properly mapped to world coordinates.
+*******************************************************************************/
+void updateWorkspace(){
+    GLdouble modelview[16];
+    GLdouble projection[16];
+    GLint viewport[4];
+
+    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
+    glGetDoublev(GL_PROJECTION_MATRIX, projection);
+    glGetIntegerv(GL_VIEWPORT, viewport);
+	
+	hlMatrixMode(HL_TOUCHWORKSPACE);
+    hlLoadIdentity();
+    
+    // Fit haptic workspace to view volume.
+    hluFitWorkspace(projection);
+
+    // Compute cursor scale.
+	gManager->gCursorScale = hluScreenToModelScale(modelview, projection, viewport);
+	gManager->gCursorScale *= CURSOR_SIZE_PIXELS;
+
+	hduVector3Dd p0, p1;
+    bool bNoError;
+
+    bNoError = fromScreen(hduVector3Dd(0, 0, 0), p0);
+    assert(bNoError);
+    
+    bNoError = fromScreen(hduVector3Dd(1, 1, 0), p1);
+    assert(bNoError);
+
+    double m_windowTworldScale = (p1 - p0).magnitude() / sqrt(2.0);
+	gManager->gWorldScale = m_windowTworldScale;
+}
+/*******************************************************************************
+ GLUT callback for idle state.  Use this as an opportunity to request a redraw.
+ Checks for HLAPI errors that have occurred since the last idle check.
+*******************************************************************************/
+void idleCallback(){
+    HLerror error;
+
+    while (HL_ERROR(error = hlGetError())){
+		std::cerr <<  "HL Error: " << error.errorCode << std::endl <<error.errorInfo << std::endl;
+        
+        if (error.errorCode == HL_DEVICE_ERROR){
+            hduPrintError(stderr, &error.errorInfo, "Device error\n");
+			std::cout << "sending error message to haptic listener" << std::endl;
+			hapticCommandCB('e',0,0,0,0,0); 
+        }
+    }
+    glutPostRedisplay();
+}
+
+/*******************************************************************************
+ This handler is called when the application is exiting.  Deallocates any state 
+ and cleans up.
+*******************************************************************************/
+void exitProcedure(){
+    
+    // Free up the haptic rendering context.
+    hlMakeCurrent(NULL);
+	
+	if (HapticManager::ghHLRC != NULL){
+        hlDeleteContext(HapticManager::ghHLRC);
+    }
+
+    // Free up the haptic device.
+    if (HapticManager::ghHD != HD_INVALID_HANDLE){
+        hdDisableDevice(HapticManager::ghHD);
+    }
+	std::cout << "freeing haptic resources" << std::endl;
+}
+
+/* initialize all the variable needed for the jni access to the Haptics class from the openGL thread*/
+void initJniVariables(void){
+	/* --- CLASSES --- */
+	//this class
+	hapticClass = env->GetObjectClass(*lock);
+	if(hapticClass == NULL){
+		stopExecution("Could not find the Haptics class");
+	}
+	// the haptic listener, member of this class
+	hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/HapticListener;");
+	if(hapticListenerClass == NULL){
+		stopExecution("Could not find the haptic listener class");
+	}
+
+	/* --- FIELD IDS --- */
+	// boolean set by the java thread when an element is added and it needs a new id from the haptic library
+	newHapticIdfieldId = env->GetFieldID(hapticClass,"newHapticId", "Z");
+	if(newHapticIdfieldId == NULL){
+		stopExecution("failed to find the newHapticId field id");
+	}
+
+	// boolean set by the java thread when an element is added and it needs a new id from the haptic library
+	dumpHapticIdfieldId = env->GetFieldID(hapticClass,"dumpHapticId", "Z");
+	if(dumpHapticIdfieldId == NULL){
+		stopExecution("failed to find the dumpHapticId field id");
+	}
+
+	// boolean set to this thread to notify the java thread the unsuccessful initialization of haptic device
+	hapticInitFailedfieldId = env->GetFieldID(hapticClass,"hapticInitFailed", "Z");
+	if(hapticInitFailedfieldId == NULL){
+		stopExecution("failed to find the hapticInitFailedfieldId field id");
+	}
+
+	// boolean set by the java thread to notify this thread the program has been shut down 
+	shutdownfieldId = env->GetFieldID(hapticClass, "shutdown", "Z");
+	if(shutdownfieldId == NULL){
+		stopExecution("failed to find the shutdownfieldId field id");
+	}
+
+	// boolean set by the java thread when the user asks to sna
+	attractTofieldId = env->GetFieldID(hapticClass, "attractTo" , "Z");
+	if(shutdownfieldId == NULL){
+		stopExecution("failed to find the attractTo field id");
+	}
+
+	attractToHapticIdFieldId = env->GetFieldID(hapticClass, "attractToHapticId", "I");
+	if(attractToHapticIdFieldId == NULL){
+		stopExecution("failed to find the attractToHapticId field id");
+	}
+
+	pickUpfieldId = env->GetFieldID(hapticClass,"pickUp","Z");
+	if(pickUpfieldId == NULL){
+		stopExecution("failed to find the pickUp field id");
+	}
+
+	pickUpHapticIdFieldId = env->GetFieldID(hapticClass,"pickUpHapticId","I");
+	if(pickUpHapticIdFieldId == NULL){
+		stopExecution("failed to find pickUpHapticId field id");
+	}
+
+	hapticListenerFieldId = env->GetFieldID(hapticClass, "hapticListener", "Luk/ac/qmul/eecs/ccmi/haptics/HapticListener;");
+	if(hapticListenerFieldId == NULL){
+		stopExecution("failed to find the hapticListener field id");
+	}
+
+	// variable to exchange values between threads
+	currentHapticIdfieldId = env->GetFieldID(hapticClass,"currentHapticId","I");
+	if(currentHapticIdfieldId == NULL){
+		stopExecution("failed to find the currentHapticId field");
+	}
+
+	cmdFieldId = env->GetFieldID(hapticListenerClass,"cmd", "C");
+	if(cmdFieldId == NULL){
+		stopExecution("failed to find the cmd field id of the hapticListener class");
+	}
+
+	diagramElementFieldId = env->GetFieldID(hapticListenerClass, "diagramElementID", "I");
+	if(diagramElementFieldId == NULL){
+		stopExecution("failed to find the diagramElement field id of the hapticListener class");
+	}
+
+	xFieldId = env->GetFieldID(hapticListenerClass, "x", "D");
+	if(xFieldId == NULL){
+		stopExecution("failed to find the x field id of the hapticListener class");
+	}
+
+	yFieldId = env->GetFieldID(hapticListenerClass, "y", "D");
+	if(yFieldId == NULL){
+		stopExecution("failed to find the y field id of the hapticListener class");
+	}
+
+	startXFieldId = env->GetFieldID(hapticListenerClass, "startX", "D");
+	if(startXFieldId == NULL){
+		stopExecution("failed to find the x field id of the hapticListener class");
+	}
+
+	startYFieldId = env->GetFieldID(hapticListenerClass, "startY", "D");
+	if(startYFieldId == NULL){
+		stopExecution("failed to find the y field id of the hapticListener class");
+	}
+
+	hapticListener = env->GetObjectField(*lock,hapticListenerFieldId);
+	/* --- METHOD IDs --- */
+	// notify method
+	notifyMethodId = env->GetMethodID(hapticClass,"notify","()V");
+	if(notifyMethodId == NULL){
+		stopExecution("failed to find the notify method id");
+	}
+
+	notifyListenerMethodId = env->GetMethodID(hapticListenerClass,"notify","()V");
+	if(notifyListenerMethodId == NULL){
+		stopExecution("failed to find the notify method id");
+	}
+
+	waitMethodId = env->GetMethodID(hapticListenerClass,"wait","()V");
+	if(waitMethodId == NULL){
+		stopExecution("failed to find the wait method id");
+	}
+}
+
+void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y, const jdouble startX, const jdouble startY){
+	/* the haptic listener java thread is waiting for commands
+	   first set the variable, the Haptic Listener java thread will read after being notified,
+	   then notify and get it awake. Thus wait for the java thread to notify that the command 
+	   has been accomplished. This is done as otherwise some commands might be neglected. as if the thread 
+	   scheduler decides to execute twice this routine without executing the java thread in the middle then 
+	   the former command gets overwritten by the latter. 
+	   Note the monitor is hapticListener and not haptics as for the draw function. 
+	   When in this routine, this thread does not hold the lock on haptics as if a command results in changing
+	   the elements collections (e.g. moveNode) all those methods are synchronized and require to acquire the lock on haptics.
+	   Since this thread would wait for the command to be executed by the java thread, which in turns would wait for this
+	   thread to release the lock on haptics, that would result in a deadlock. 
+	 */
+	/* now wake up the haptic listener */
+	if(env->MonitorEnter(hapticListener) != JNI_OK){
+		stopExecution("Could not allocate memory for haptic listener thread monitor");
+	}
+	checkExceptions(env,"Could not enter monitor on the haptic listener");
+	env->SetCharField(hapticListener,cmdFieldId,cmd);
+	env->SetIntField(hapticListener,diagramElementFieldId,ID);
+	env->SetDoubleField(hapticListener,xFieldId,x);
+	env->SetDoubleField(hapticListener,yFieldId,y);
+	env->SetDoubleField(hapticListener,startXFieldId,startX);
+	env->SetDoubleField(hapticListener,startYFieldId,startY);
+	// wake the java thread up to execute the command
+	env->CallVoidMethod(hapticListener,notifyListenerMethodId);
+	checkExceptions(env, "Could not call notify() on HapticListener");
+	/* wait for the commands to be executed. Here is actually where the monitor is
+	 * freed and the java thread starts to execute the command, having been notified */ 
+	env->CallVoidMethod(hapticListener,waitMethodId);
+	checkExceptions(env, "Could not call wait() on HapticListener");
+	if(env->MonitorExit(hapticListener) != JNI_OK){
+		stopExecution("Could not release memory for haptic listener thread monitor");
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_Haptics.h	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,27 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics */
+
+#ifndef _Included_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics
+#define _Included_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MIN_PRIORITY
+#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MIN_PRIORITY 1L
+#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_NORM_PRIORITY
+#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_NORM_PRIORITY 5L
+#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MAX_PRIORITY
+#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MAX_PRIORITY 10L
+/*
+ * Class:     uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics
+ * Method:    init
+ * Signature: (II)I
+ */
+JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_init
+  (JNIEnv *, jobject, jint, jint);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/utils.cpp	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,73 @@
+#include "utils.h"
+
+void checkExceptions(JNIEnv *env, char* what){
+	if(env->ExceptionOccurred()){
+		std::cout << "Exception occurred!!!" << std::endl;
+		std::cout << what << std::endl;
+		env->ExceptionDescribe();
+		exit(-1);
+	}
+}
+
+void stopExecution(char* msg){
+	std::cerr << msg << std::endl;
+	exit(-1);
+}
+
+
+bool fromScreen(const hduVector3Dd &win, hduVector3Dd &obj) {
+
+	hduMatrix m_worldTview;
+    hduMatrix m_viewTclip;
+    int m_viewport[4];
+	glGetDoublev(GL_MODELVIEW_MATRIX, m_worldTview);
+    glGetDoublev(GL_PROJECTION_MATRIX, m_viewTclip);
+    glGetIntegerv(GL_VIEWPORT, m_viewport); 
+    int nResult =	gluUnProject(
+        win[0], win[1], win[2],
+		m_worldTview,
+        m_viewTclip,
+        m_viewport,
+        &obj[0], &obj[1], &obj[2]);
+    
+    return nResult == GL_TRUE;
+}
+
+bool toScreen(const hduVector3Dd &obj, hduVector3Dd &win)  {
+	
+	hduMatrix m_worldTview;
+    hduMatrix m_viewTclip;
+    int m_viewport[4];
+	glGetDoublev(GL_MODELVIEW_MATRIX, m_worldTview);
+    glGetDoublev(GL_PROJECTION_MATRIX, m_viewTclip);
+    glGetIntegerv(GL_VIEWPORT, m_viewport); 
+    int nResult = gluProject(
+        obj[0], obj[1], obj[2],
+		m_worldTview,
+        m_viewTclip,
+        m_viewport,
+        &win[0], &win[1], &win[2]);
+
+    return nResult == GL_TRUE;
+}
+
+
+HDdouble norm(HDdouble v[3]){
+	return sqrt( (v[0]*v[0])+(v[1]*v[1])+(v[2]*v[2]));
+}
+
+void print(char* str){
+	std::cout << str << std::endl;
+}
+
+void print(int str){
+	std::cout << str << std::endl;
+}
+
+float rotate(){
+	static float rot = 0.0f;
+	rot += 0.5f;
+	if(rot > 360)
+		rot = 0;
+	return rot;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/PhantomOmni/utils.h	Wed Apr 25 17:09:09 2012 +0100
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <jni.h>
+#include "stdafx.h"
+
+void checkExceptions(JNIEnv *env, char* what);
+void stopExecution(char* msg);
+bool fromScreen(const hduVector3Dd &win, hduVector3Dd &obj);
+bool toScreen(const hduVector3Dd &obj, hduVector3Dd &win);
+HDdouble norm(HDdouble v[3]);
+
+//TO REMOVE
+void print(char* str);
+float rotate();
+void print(int str);
--- a/native/WinNarrator/WinNarrator.vcproj	Mon Feb 06 12:54:06 2012 +0000
+++ b/native/WinNarrator/WinNarrator.vcproj	Wed Apr 25 17:09:09 2012 +0100
@@ -18,7 +18,7 @@
 	<Configurations>
 		<Configuration
 			Name="Debug|Win32"
-			OutputDirectory="..\workspace\ccmi"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
 			IntermediateDirectory="$(ConfigurationName)"
 			ConfigurationType="2"
 			CharacterSet="1"