changeset 0:9418ab7b7f3f

Initial import
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Fri, 16 Dec 2011 17:35:51 +0000
parents
children 71ff0735df5a
files java/.classpath java/.project java/.settings/org.eclipse.jdt.core.prefs java/libs/JWizardComponent.jar java/libs/NetUtil.jar java/libs/beads.jar java/libs/jl1.0.1.jar java/libs/mp3spi1.9.4.jar java/libs/tritonus_share.jar java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionEvent.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/ConnectNodesException.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/DiagramElementComparator.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/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/ElementNotifier.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/Diagram.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/ExtensionFilter.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/GraphToolbar.java java/src/uk/ac/qmul/eecs/ccmi/gui/Grid.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/Lock.java java/src/uk/ac/qmul/eecs/ccmi/gui/LoopComboBox.java java/src/uk/ac/qmul/eecs/ccmi/gui/LoopSpinnerNumberModel.java java/src/uk/ac/qmul/eecs/ccmi/gui/ModifierEditorDialog.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/PropertyTableModel.java java/src/uk/ac/qmul/eecs/ccmi/gui/ResourceFactory.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/SpeechSummaryPane.java java/src/uk/ac/qmul/eecs/ccmi/gui/TemplateEditor.java java/src/uk/ac/qmul/eecs/ccmi/gui/ccmi_favicon.gif java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileChooser.java java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileChooserFactory.java java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTree.java java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTreeCellRenderer.java java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTreeNode.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/filechooser/TreeSelectionTextField.java java/src/uk/ac/qmul/eecs/ccmi/gui/license.txt java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.properties java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PrototypePersistenceDelegate.java java/src/uk/ac/qmul/eecs/ccmi/haptics/DummyHaptics.java java/src/uk/ac/qmul/eecs/ccmi/haptics/Edge.java java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListener.java java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListenerCommand.java 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/main/DiagramEditorApp.java java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.properties java/src/uk/ac/qmul/eecs/ccmi/network/CCmIOSCBundle.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/DiagramAlreadySharedException.java java/src/uk/ac/qmul/eecs/ccmi/network/DiagramDownloader.java java/src/uk/ac/qmul/eecs/ccmi/network/DiagramShareException.java java/src/uk/ac/qmul/eecs/ccmi/network/LockMessage.java java/src/uk/ac/qmul/eecs/ccmi/network/LockMessageConverter.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/OscProtocol.java java/src/uk/ac/qmul/eecs/ccmi/network/Protocol.java java/src/uk/ac/qmul/eecs/ccmi/network/ProtocolFactory.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/network/ServerNotRunningException.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/ArrowHead.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/CircleNode.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EdgeDrawSupport.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EllipticalNode.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Model.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/ModifierView.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/MultiLineString.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/PropertyView.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/RectangularNode.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeEdge.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapePrototypePersistenceDelegate.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleTemplateEditor.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.properties java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardPanel.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SquareNode.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/TriangularNode.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Wizard.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Circle.mp3 java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Ellipse.mp3 java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Rectangle.mp3 java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Square.mp3 java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Triangle.mp3 java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/dashedLine.mp3 java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/dottedLine.mp3 java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/straightLine.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/AudioResourcesService.java java/src/uk/ac/qmul/eecs/ccmi/sound/BeadsSound.java java/src/uk/ac/qmul/eecs/ccmi/sound/PlayerListener.java java/src/uk/ac/qmul/eecs/ccmi/sound/Sound.java java/src/uk/ac/qmul/eecs/ccmi/sound/SoundEvent.java java/src/uk/ac/qmul/eecs/ccmi/sound/SoundFactory.java java/src/uk/ac/qmul/eecs/ccmi/sound/audio/Ok.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/cancel.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/collapse.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/drag.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/editingMode.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/endoflist.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/error.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/expand.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/hookOFF.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/hookON.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/jump.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/magnetOFF.mp3 java/src/uk/ac/qmul/eecs/ccmi/sound/audio/magnetON.mp3 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/NarratorException.java java/src/uk/ac/qmul/eecs/ccmi/speech/NarratorFactory.java 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/WinNarrator.dll java/src/uk/ac/qmul/eecs/ccmi/utils/CCmIUncaughtExceptionHandler.java java/src/uk/ac/qmul/eecs/ccmi/utils/CharEscaper.java java/src/uk/ac/qmul/eecs/ccmi/utils/ExceptionHandler.java 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/OsDetector.java java/src/uk/ac/qmul/eecs/ccmi/utils/Pair.java java/src/uk/ac/qmul/eecs/ccmi/utils/PreferencesService.java java/src/uk/ac/qmul/eecs/ccmi/utils/Validator.java license.txt native/WinNarrator/ReadMe.txt native/WinNarrator/WinNarrator.cpp native/WinNarrator/WinNarrator.vcproj native/WinNarrator/stdafx.cpp native/WinNarrator/stdafx.h native/WinNarrator/targetver.h native/WinNarrator/uk_ac_qmul_eecs_ccmi_speech_NativeWinNarrator.cpp native/WinNarrator/uk_ac_qmul_eecs_ccmi_speech_NativeWinNarrator.h
diffstat 182 files changed, 26530 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/.classpath	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<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/jl1.0.1.jar"/>
+	<classpathentry kind="lib" path="libs/JWizardComponent.jar"/>
+	<classpathentry kind="lib" path="libs/mp3spi1.9.4.jar"/>
+	<classpathentry kind="lib" path="libs/NetUtil.jar"/>
+	<classpathentry kind="lib" path="libs/tritonus_share.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/.project	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>ccmieditor</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/.settings/org.eclipse.jdt.core.prefs	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,12 @@
+#Fri Dec 16 13:28:00 GMT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
Binary file java/libs/JWizardComponent.jar has changed
Binary file java/libs/NetUtil.jar has changed
Binary file java/libs/beads.jar has changed
Binary file java/libs/jl1.0.1.jar has changed
Binary file java/libs/mp3spi1.9.4.jar has changed
Binary file java/libs/tritonus_share.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionEvent.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,52 @@
+/*  
+ 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 object representing a collection event. Collection events happen when a Diagram 
+ * Element is either inserted or removed from the DiagramModel via the CollectionModel.  
+ *
+ */
+@SuppressWarnings("serial")
+public class CollectionEvent extends EventObject {
+
+	/**
+	 * 
+	 * @param source the source of the event
+	 * @param element the diagram element that has been added or removed from the collection
+	 */
+	public CollectionEvent(Object source, DiagramElement element) {
+		super(source);
+		this.element = element;
+	}
+	
+	/**
+	 * 
+	 * @return the diagram element whose addition or removal from the collection
+	 * triggered this event.  
+	 */
+	public DiagramElement getDiagramElement(){
+		return element;
+	}
+	
+	private DiagramElement element;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionListener.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,34 @@
+/*  
+ 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;
+
+/**
+ * A listener to a collection event. A collection event is triggered whenever a diagram element
+ * is added or removed from the collection or when a diagram element contained in the collection is 
+ * changed (e.g. when a new Name for the element is set)
+ *
+ */
+public interface CollectionListener {
+	
+	void elementInserted(CollectionEvent e);
+	
+	void elementTakenOut(CollectionEvent e);
+	
+	void elementChanged(ElementChangedEvent e);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionModel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,126 @@
+/*  
+ 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.Collection;
+
+import javax.swing.event.ChangeListener;
+
+/**
+ * 
+ * Represents the collection side of a DiagramModel instance. 
+ *
+ * @param <N> a type extending DiagramNode
+ * @param <E> a type extending DiagramEdge
+ */
+public interface CollectionModel<N extends DiagramNode, E extends DiagramEdge> {
+	/**
+	 * Adds a collection listener to the model.  
+	 * @param listener the listener to be added
+	 */
+	void addCollectionListener(CollectionListener listener);
+	/**
+	 * Removed a collection listener to the model.  
+	 * @param listener the listener to be removed
+	 */
+	void removeCollectionListener (CollectionListener listener);
+	
+	/**
+	 * insert a DiagramNode into the diagram model
+	 * @param n the DiagramNode to be inserted in the collection
+	 * @return true if this collection changed as a result of the call
+	 */
+	boolean insert(N n) ;
+	
+	/**
+	 * insert a DiagramEdge into the diagram model
+	 * @param e the DiagramNode to be inserted in the collection
+	 * @return true if this collection changed as a result of the call
+	 */
+	boolean insert(E e);
+	
+	/**
+	 * Removes a DiagramElement from the model
+	 * @param e the diagramElement to be removed
+	 * @return true if this collection changed as a result of the call
+	 */
+	boolean takeOut(DiagramElement e);
+	
+	/**
+	 * 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  
+	 * @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
+	 */
+	Collection<DiagramElement> getElements();
+	
+	/**
+	 * Add a change listener to the model. 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  
+	 */
+	void addChangeListener(ChangeListener l);
+	
+	/**
+	 * Removes a change listener from the model. 
+	 * @param l a ChangeListener to remove from the model
+	 */
+	void removeChangeListener(ChangeListener l);
+	
+	/**
+	 * Returns true if the model has been modified
+	 * @return true if the model has been modified
+	 */
+	boolean isModified();
+	
+	/**
+	 * Sets the model as unmodified. This entails that {@link #isModified()} will return 
+	 * false unless the model doesn't get modified again. After this call a new modification
+	 * of the model would trigger the associated change listeners again. 
+	 */
+	void setUnmodified();
+	
+	/**
+	 * Sorts the nodes and edges is the model. The ordering method is given by a diagram 
+	 * element comparator.
+	 * @see DiagramElementComparator
+	 */
+	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
+	 */
+	Object getMonitor();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ConnectNodesException.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,33 @@
+/*  
+ 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;
+
+
+/**
+ * Represents the exception that is raised when a number of nodes are    
+ * connected through an edge which allows for a different number of nodes only.  
+ * 
+ * @see DiagramEdge#connect(java.util.List)
+ */
+@SuppressWarnings("serial")
+public class ConnectNodesException extends Exception {
+	public ConnectNodesException(String message){
+		super(message);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramEdge.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,169 @@
+/*  
+ 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.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class represent an edge in the diagram model. Note that this class is 
+ * a tree node.
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class DiagramEdge extends DiagramElement {
+	/**
+	 * 
+	 * @param type the type of the edge 
+	 * @param availableEndDescriptions the end descriptions this edge allows 
+	 */
+	public DiagramEdge(String type, String[] availableEndDescriptions){
+		setType(type);
+		this.availableEndDescriptions = availableEndDescriptions;
+		endLabels = new LinkedHashMap<DiagramNode,String>();
+		endDescriptions = new LinkedHashMap<DiagramNode,Integer>();
+	}
+
+	/**
+	 * Returns a more detailed description of the edge than {@link #spokenText()}.
+	 * the description includes which nodes this edge is connecting 
+	 * 
+	 *  @return a description of the edge
+	 */
+	@Override
+	public String detailedSpokenText(){
+		final String and = " and ";
+		StringBuilder b = new StringBuilder(getType());
+		b.append(' ').append(spokenText());
+		b.append(". Connecting ");
+		for(int i=0; i<getChildCount();i++){
+			b.append(((DiagramModelTreeNode)getChildAt(i)).getName());
+			b.append(and);
+		}
+		// remove the last " and "
+		b.delete(b.length()-and.length(), b.length());
+		return  b.toString();
+	}
+
+	/**
+	 * Set a label related to a node. On a graphical representation of the diagram 
+	 * 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 void setEndLabel(DiagramNode n, String label){
+		if(label == null)
+			label = "";
+		endLabels.put(n, label);
+		notifyChange(new ElementChangedEvent(n,this,"endLabel"));
+	}
+
+	/**
+	 * Returns the end label related to a node. On a graphical representation of the diagram 
+	 * 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);
+		if(s == null)
+			return "";
+		return s;
+	}
+
+	/**
+	 * Returns an array with all the available end description this edge can be 
+	 * assigned. 
+	 * @return an array of string available end description 
+	 */
+	public String[] getAvailableEndDescriptions(){
+		return this.availableEndDescriptions;
+	}   
+	
+	/**
+	 * Set a string describing the end related to a node. On a visual diagram this 
+	 * corresponds to an arrow.
+	 * 
+	 * @param n the node at the edge end whose description will be set
+	 * @param index an index of the array returned by getAvailableEndDescriptions(). The 
+	 * edge end description will be set with the string at that position in the array.
+	 * if index is equal to NO_END_DESCRIPTION_INDEX, then the description will be set 
+	 * as the empty string.
+	 * 
+	 */
+	public void setEndDescription(DiagramNode n, int index){
+		endDescriptions.put(n, index);
+		notifyChange(new ElementChangedEvent(n,this,"arrowHead"));
+	}
+
+	/**
+	 * Returns a string describing the end related to a node. On a visual diagram this 
+	 * corresponds to an arrow.
+	 *    
+	 * @param n the node at the edge end whose description will be returned   
+	 * @return a description string
+	 */
+	public String getEndDescription(DiagramNode n){
+		Integer index = endDescriptions.get(n);
+		if(index == null || index == NO_END_DESCRIPTION_INDEX)
+			return "";
+		return availableEndDescriptions[endDescriptions.get(n)];
+	}
+
+	/**
+	 * Returns the connected node at the specified index  
+	 * @param index an index into node's list
+	 * @return the connected node at the specified index
+	 */
+	public abstract DiagramNode getNodeAt(int index);
+
+	/**
+	 * Returns the number of nodes this edge is connecting
+	 * @return the number of nodes this edge is connecting
+	 */
+	public abstract int getNodesNum();
+
+	/**
+	 * Removes a node from this edge. On a graphical representation of the diagram 
+	 * this would mean that the node is no longer connected to the other nodes via this edge. 
+	 * @param n the node to be removed
+	 * @return true if the inner collection changed as a result of the call
+	 */
+	public abstract boolean removeNode(DiagramNode n);
+	
+	/**
+	 * Connect a list of nodes with this edge 
+	 * @param nodes a list of nodes to connect 
+	 * @throws ConnectNodesException if the number of nodes in the list is different 
+	 * from the number allowed by this edge.  
+	 */
+	public abstract void connect(List<DiagramNode> nodes) throws ConnectNodesException;
+
+	/**
+	 * An index to be passed to {@link #setEndDescription(DiagramNode, int)} 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;
+	private Map<DiagramNode,String> endLabels;
+	private Map<DiagramNode,Integer> endDescriptions;
+	private String[] availableEndDescriptions;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramElement.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,173 @@
+/*  
+ 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.io.InputStream;
+
+/**
+ * A Diagram Element is either a node or an edge of the diagram. It's an abstract 
+ * class which is extended by DiagramEdge and DiagramNode.    
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class DiagramElement extends DiagramModelTreeNode implements Cloneable{
+	
+	protected DiagramElement(){
+		name = "";
+		id = NO_ID;
+		notifier = DUMMY_NOTIFIER; // initially set to no effect notifier
+	}
+	
+	/**
+	 * 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.  
+	 * @return the type of this element 
+	 */
+	public String getType(){
+		return type;
+	}
+	
+	/**
+	 * 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){
+		 this.type = type;
+	}
+	
+	/**
+	 * Notifies the model of a changed that has happened on this element. If this element is not 
+	 * held by any model than this method will have no effect. 
+	 * @param evt an event representing the fact that the element is changed
+	 */
+	protected void notifyChange(ElementChangedEvent evt){
+		notifier.notifyChange(evt);
+	}
+	
+	/**
+	 * 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(){
+		if(name.isEmpty() && id != NO_ID)
+			return "new " + getType() + " " + id;
+		return name;
+	}
+	   
+	/**
+	 * Sets the name of this element instance.
+	 * 
+	 * @param the string to set as the name 
+	 */
+	public void setName(String s){
+		String name = s;
+		/* if the user enters an empty string we go back to the default name */
+		if(s.isEmpty() && id != NO_ID){
+			name = "new " + getType() + " " + id;
+		}		
+		setUserObject(name);
+		this.name = name;
+		notifyChange(new ElementChangedEvent(this,this,"name"));
+	}
+	
+	/**
+	 * Returns an InputStream to a sound file with the sound of this element 
+	 * @return an InputStream 
+	 */
+	public abstract InputStream getSound();
+	
+	/**
+	 * Sets the if for this element. The id is a number which uniquely identifies this instance
+	 * within a DiagramModel. 
+	 * Unlike the name, which can be the same for two different instances.  
+	 * @param id a long number which must be greater than 0
+	 * @throws IllegalArgumentException id the id passe as argument is lower or equal to 0.
+	 */
+	public void setId(long id){
+		if (id < NO_ID)
+			throw new IllegalArgumentException();
+		else 
+			this.id = id;
+		if(name.isEmpty() && id != NO_ID){
+			String s = "new " + getType() + " " + id; 
+			this.name = s;
+			setUserObject(s);
+		}
+	}
+	
+	/**
+	 * Returns the id of this instance of DiagramElement.
+	 * @return a long representing the id of this instance of the element 
+	 * or NO_ID if it hasn't got one. 
+	 */
+	public long getId(){
+		return id;
+	}
+		
+	/** 
+	 *  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){
+		this.notifier = notifier;
+	}
+	
+	@Override
+	public Object clone(){
+		DiagramElement clone = (DiagramElement)super.clone();
+		clone.name = "";
+		clone.id = NO_ID;
+		return clone;
+	}
+	
+	/**
+	 * Returns a description of the DiagramElement passed as argument, suitable 
+	 * for logging purposes. 
+	 * 
+	 * @param de the diagram element to log stuff about 
+	 * @return a log entry describing the element passed as agument
+	 */
+	public static String toLogString(DiagramElement de){
+		StringBuilder builder = new StringBuilder(de.getName());
+		builder.append('(');
+		if(de.getId() == DiagramElement.NO_ID)
+			builder.append("no id");
+		else
+			builder.append(de.getId());
+		builder.append(')');	
+		return builder.toString();
+	}
+	
+	private long id = NO_ID;
+	private ElementNotifier notifier;
+	private String type;
+	private String name;
+	private static final ElementNotifier DUMMY_NOTIFIER = new ElementNotifier(){
+		@Override
+		public void notifyChange(ElementChangedEvent evt) {}
+	};
+	/**
+	 * The value returned by getId() if the element instance has not been assigned any id 
+	 */
+	public static final long NO_ID = 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramElementComparator.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,46 @@
+/*  
+ 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.Comparator;
+
+/**
+ * A Comparator for diagram elements. The elements are ordered by their id.  
+ *
+ * @see DiagramElement#getId()
+ */
+public class DiagramElementComparator implements Comparator<DiagramElement> {
+	public static DiagramElementComparator getInstance(){
+		if(comparator == null)
+			comparator = new DiagramElementComparator();
+		return comparator;
+	}
+	
+	@Override
+	public int compare(DiagramElement de1, DiagramElement de2) {
+		if(de1.getId() == de2.getId())
+			return 0;
+		else if(de1.getId() < de2.getId())
+			return -1;
+		else 
+			return 1;
+	}	
+
+	private static DiagramElementComparator comparator;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,781 @@
+/*  
+ 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.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+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. 
+ * The model is "double sided" in the sense that it can be accessed through either 
+ * a CollectionModel or a TreeModel returned by the respective getter methods.
+ * The TreeModel is suitable for JTree classes of the swing library, while 
+ * the CollectionModel can be used by view classes by registering a CollectionListener 
+ * to the CollectionModel itself.    
+ * It is important to notice that changes made on one side will reflect on the other, 
+ * eventually triggering the registered listeners.
+ * The tree model is structured according to a special layout which is suitable for 
+ * browsing the tree view via audio interface ( text to speech synthesis and sound).
+ *
+ * @param <N> a subclass of DiagramNode 
+ * @param <E> a subclass of DiagramEdge
+ */
+public class DiagramModel<N extends DiagramNode, E extends DiagramEdge>{
+	/**
+	 * Create a model instance starting from some nodes and edges prototypes. 
+	 * All subsequently added element must be clones of such prototypes.
+	 * @param nodePrototypes
+	 * @param edgePrototypes
+	 */
+	@SuppressWarnings("serial")
+	public DiagramModel(N [] nodePrototypes, E [] edgePrototypes) {
+		root = new DiagramModelTreeNode(ROOT_LABEL){
+			@Override
+			public boolean isRoot(){
+				return true;
+			}
+		};
+		modified = false;
+		
+		nodeCounter = 0;
+		edgeCounter = 0;
+		
+		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
+	 * 
+	 * @return a CollectionModel for this diagram 
+	 */
+	public CollectionModel<N,E> getDiagramCollection(){
+		return diagramCollection;
+	}
+	
+	/**
+	 * Returns a TreeModel for this diagram
+	 * 
+	 * @return a TreeModel for this diagram 
+	 */
+	public TreeModel<N,E> getTreeModel(){
+		return treeModel;
+	}	
+	
+	private void triggerModification(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());
+		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);
+		}
+		
+		@Override
+		public boolean insert(E e){
+			return _insert(e,this);
+		}
+		
+		@Override
+		public boolean takeOut(DiagramElement element){
+			if(element instanceof DiagramNode)
+				return _takeOut((DiagramNode)element,this);
+			if(element instanceof DiagramEdge)
+				return _takeOut((DiagramEdge)element,this);
+			return false;
+		}
+
+		@Override
+		public void addCollectionListener(CollectionListener listener) {
+			listeners.add(listener);
+		}
+
+		@Override
+		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);
+			}
+		}
+
+		@Override
+		public Collection<N> getNodes() {
+			return Collections.unmodifiableCollection(nodes);
+		}
+
+		@Override
+		public Collection<E> getEdges() {
+			return Collections.unmodifiableCollection(edges);
+		}
+		
+		@Override
+		public Collection<DiagramElement> getElements(){
+			return Collections.unmodifiableCollection(elements);
+		}
+
+		@Override
+		public Object getMonitor(){
+			return DiagramModel.this;
+		}
+		
+		@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;
+	    }
+		
+		protected ArrayList<CollectionListener> listeners;
+
+	}
+    
+    @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);
+		}
+
+		@Override
+		public boolean takeTreeNodeOut(DiagramElement treeNode){
+			boolean result;
+			if(treeNode instanceof DiagramEdge){
+				result = _takeOut((DiagramEdge)treeNode,this);
+			}
+			else{
+				result = _takeOut((DiagramNode)treeNode,this);
+			}
+			/* remove the bookmarks associated with the just deleted diagram element, if any */
+			for(String key : treeNode.getBookmarkKeys())
+				bookmarks.remove(key);
+			return result;
+		}
+
+		@Override
+		public DiagramModelTreeNode putBookmark(String bookmark, DiagramModelTreeNode treeNode){
+			if(bookmark == null)
+				throw new IllegalArgumentException("bookmark cannot be null");
+			setEventSource(this);
+			treeNode.addBookmarkKey(bookmark);
+			DiagramModelTreeNode result =  bookmarks.put(bookmark, treeNode);
+			nodeChanged(treeNode);
+			iLog("bookmark added",bookmark);
+			triggerModification(this);
+			return result;
+		}
+
+		@Override
+		public DiagramModelTreeNode getBookmarkedTreeNode(String bookmark) {
+			return bookmarks.get(bookmark);
+		}
+		
+		@Override
+		public DiagramModelTreeNode removeBookmark(String bookmark) {
+			setEventSource(this);
+			DiagramModelTreeNode treeNode = bookmarks.remove(bookmark);
+			treeNode.removeBookmarkKey(bookmark);
+			nodeChanged(treeNode);
+			iLog("bookmark removed",bookmark);
+			triggerModification(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);
+			nodeChanged(treeNode);
+			iLog("notes set for "+treeNode.getName(),"".equals(notes) ? "empty notes" : notes.replaceAll("\n", "\\\\n"));
+			triggerModification(this);
+		}
+		
+		private void setEventSource(Object source){
+			this.src = source;
+		}
+		
+		@Override
+		public Object getMonitor(){
+			return DiagramModel.this;
+		}
+		
+		@Override
+		public void addChangeListener(ChangeListener l){
+			DiagramModel.this.addChangeListener(l);
+		}
+		
+		@Override
+		public void removeChangeListener(ChangeListener l){
+			DiagramModel.this.removeChangeListener(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
+		protected void fireTreeNodesChanged(Object source, Object[] path,
+				int[] childIndices, Object[] children) {
+			super.fireTreeNodesChanged(src, path, childIndices, children);
+		}
+
+		@Override
+		protected void fireTreeNodesInserted(Object source, Object[] path,
+				int[] childIndices, Object[] children) {
+			super.fireTreeNodesInserted(src, path, childIndices, children);
+		}
+
+		@Override
+		protected void fireTreeNodesRemoved(Object source, Object[] path,
+				int[] childIndices, Object[] children) {
+			super.fireTreeNodesRemoved(src, path, childIndices, children);
+		}
+
+		@Override
+		protected void fireTreeStructureChanged(Object source, Object[] path,
+				int[] childIndices, Object[] children) {
+			super.fireTreeStructureChanged(src, path, childIndices, children);
+		}
+		
+		public boolean isModified(){
+			return modified;
+		}
+		
+		public void setUnmodified(){
+	    	modified = false;
+	    }
+		
+		private Object src;
+		private Map<String, DiagramModelTreeNode> bookmarks;		
+    }
+    
+    private synchronized 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());
+			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);
+
+		/* insert node into tree which fires tree listeners */
+		treeModel.insertNodeInto(n, parent, parent.getChildCount());
+		/* this is necessary to increment the child counter displayed between brackets */
+		treeModel.nodeChanged(parent);
+		diagramCollection.fireElementInserted(source,n);
+		triggerModification(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 */
+		_removeInternalNodes(n,source);
+		/* clear external node and clear edges attached to this node and updates other ends of such edges */
+		_clearNodeReferences(n,source);
+		/* remove the node from the tree (fires listeners) */
+		treeModel.removeNodeFromParent(n);
+		/* this is necessary to increment the child counter displayed between brackets */
+		treeModel.nodeChanged(n.getParent());
+		/* remove the nodes from the collection */
+		nodes.remove(n);
+		elements.remove(n);
+		/* notify all the listeners a new node has been removed */
+		diagramCollection.fireElementTakenOut(source,n);
+		triggerModification(n);
+		
+		if(nodes.isEmpty()){
+			nodeCounter = 0;
+		}else{
+			long lastNodeId = nodes.get(nodes.size()-1).getId(); 
+			if(n.getId() > lastNodeId)
+				nodeCounter = lastNodeId;
+		}
+		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 */ 
+		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());
+			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*/
+			treeModel.insertNodeInto(new EdgeReferenceMutableTreeNode(e,n), edgeType, 0);
+			/* this is necessary to increment the child counter displayed between brackets */
+    		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());
+		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);
+		
+		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);
+		/* 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 */
+		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(); 
+			if(e.getId() > lastEdgeId)
+				edgeCounter = lastEdgeId;
+    	}
+    	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 */
+		if(n.getExternalNode() != null)
+			n.getExternalNode().removeInternalNode(n);
+		/* remove edges attached to this node from the collection */
+		ArrayList<DiagramEdge> edgesToRemove = new ArrayList<DiagramEdge>(edges.size());
+		for(int i=0; i<n.getEdgesNum(); i++){
+			DiagramEdge e = n.getEdgeAt(i);
+			if(e.getNodesNum() == 2){
+				edgesToRemove.add(e);
+			}else{
+				e.removeNode(n);
+				DiagramModelTreeNode 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);
+				}
+			}
+    		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();){
+			temp = children.nextElement();
+			if(temp.getName().equals(name)){
+				 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;
+	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 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;}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModelTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,289 @@
+/*  
+ 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();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,230 @@
+/*  
+ 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.Set;
+
+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.  
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class DiagramNode extends DiagramElement {
+	public DiagramNode(String type, NodeProperties properties){
+		setType(type);
+		this.properties = properties;
+	}
+
+	/**
+	 * @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.
+	 * @return the properties of this node
+	 */
+	public NodeProperties getProperties(){
+		return properties;
+	}
+
+	/**
+	 * Returns a copy of the properties of this node. The modifying the returned object 
+	 * won't affect this Node.
+	 * @return Returns a copy of the properties of this node
+	 */
+	public NodeProperties getPropertiesCopy(){
+		NodeProperties p = (NodeProperties)properties.clone();
+		for(String type : properties.getTypes()){
+			Modifiers modifiers = properties.getModifiers(type);
+			int index = 0;
+			for(String value : properties.getValues(type)){
+				if(properties.getModifiers(type).isNull())
+					p.addValue(type, value);
+				else
+					p.addValue(type, value, modifiers.getIndexes(index++));
+			}
+		}
+		return p;
+	}
+
+	/**
+	 * Set the NodeProperties of this node
+	 * @param properties  the properties to set for this node
+	 */
+	public  void setProperties(NodeProperties properties){
+		this.properties = properties;
+		notifyChange(new ElementChangedEvent(this.properties,this,"properties"));
+	}
+
+	/**
+	 * Add a property to the NodeProperties of this node
+	 * @see NodeProperties#addValue(String, String)
+	 * 
+	 */
+	public void addProperty(String propertyType, String propertyValue){
+		getProperties().addValue(propertyType, propertyValue);
+		int index = getProperties().getValues(propertyType).size() - 1;
+		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,index),this,"property.add"));
+	}
+
+	/**
+	 * Removes a property from the NodeProperties of this node
+	 * @see NodeProperties#removeValue(String, int)
+	 */
+	public void removeProperty(String propertyType, int valueIndex){
+		getProperties().removeValue(propertyType, valueIndex);
+		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,valueIndex),this,"property.remove"));
+	}
+
+	/**
+	 * 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){
+		getProperties().setValue(propertyType, valueIndex, newValue);
+		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,valueIndex),this,"property.set"));
+	}
+
+	/**
+	 * Removes all the values in the NodeProperties of this node
+	 * @see NodeProperties#clear()
+	 */
+	public void clearProperties(){
+		getProperties().clear();
+		notifyChange(new ElementChangedEvent(this,this,"properties.clear"));
+	}
+
+	/**
+	 * 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){
+		getProperties().getModifiers(propertyType).setIndexes(propertyValueIndex, modifierIndexes);
+		notifyChange(new ElementChangedEvent(new Pair<String,Integer>(propertyType,propertyValueIndex),this,"property.modifiers"));
+	}
+
+	/**
+	 * Returns a more detailed description of the node than {@link #spokenText()}.
+	 * the description includes which how many properties the node has and 
+	 * how many edges are attached to it. 
+	 * 
+	 *  @return a description of the node
+	 */
+	@Override
+	public String detailedSpokenText(){
+		StringBuilder builder = new StringBuilder(getType());
+		builder.append(' ');
+		builder.append(getName());
+		builder.append('.').append(' ');
+		for(int i=0; i<getChildCount();i++){
+			DiagramModelTreeNode treeNode = (DiagramModelTreeNode) getChildAt(i);
+			if(treeNode.getChildCount() > 0){
+				builder.append(treeNode.getChildCount())
+				.append(' ')
+				.append(treeNode.getName())
+				.append(';')
+				.append(' ');
+			}
+		}		  
+		return builder.toString();
+	}
+
+	/**
+	 * Returns the number of attached edges
+	 * @return the number of attached edges 
+	 */
+	public abstract int getEdgesNum();
+
+	/**
+	 * Returns the attached edge at the specified index  
+	 * @param index an index into edge's list
+	 * @return the attached edge at the specified index
+	 */
+	public abstract DiagramEdge getEdgeAt(int index);
+
+	/**
+	 * add an edge to the attached edges list
+	 * @param 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 
+	 */
+	public abstract boolean removeEdge(DiagramEdge e);
+
+	/**
+	 * Gets the node this node is within.
+	 * @return the parent node, or null if the node
+	 * has no parent
+	 */
+	public abstract DiagramNode getExternalNode();
+
+	/**
+	 * Sets the node this node is within.
+	 * @param node the parent node, or null if the node
+	 * has no parent
+	 */
+	public abstract void setExternalNode(DiagramNode node);
+
+	/**
+	 * Returns the number of internal nodes
+	 * @return the number of internal nodes
+	 */
+	public abstract int getInternalNodesNum();
+	
+	/**
+	 * Gets the internal node at the specified index
+	 * @param index an index into internal nodes list
+	 * @return the internal node at the specified index
+	 */
+	public abstract DiagramNode getInternalNodeAt(int index);
+
+	/**
+	 * Adds a node to the internal nodes list.
+	 * @param node the internal node to add
+	 */
+	public abstract void addInternalNode(DiagramNode node);
+
+	/**
+	 * Removes a node from the internal nodes list.
+	 * @param node the internal node to remove
+	 */
+	public abstract void removeInternalNode(DiagramNode node);
+
+	@Override
+	public Object clone(){
+		DiagramNode clone = (DiagramNode)super.clone();
+		clone.properties = (NodeProperties)properties.clone();
+		return clone;
+	}
+
+	private NodeProperties properties;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceHolderMutableTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,58 @@
+/*  
+ 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;
+
+/**
+ * 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.
+ * 
+ *
+ */
+@SuppressWarnings("serial")
+public class EdgeReferenceHolderMutableTreeNode extends DiagramModelTreeNode {
+
+	public EdgeReferenceHolderMutableTreeNode(Object userObj){
+		super(userObj);
+	}
+	
+	@Override
+	public String toString(){
+		StringBuilder builder = new StringBuilder(super.toString());
+		builder.append(" (").append(getChildCount()).append(")");
+		return builder.toString();
+	}
+	
+	/**
+	 * 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
+	 */
+	@Override
+	public String spokenText(){
+		StringBuilder builder = new StringBuilder(getName());
+		builder.append(", ");
+		builder.append(getChildCount() == 0 ? "empty" : getChildCount());
+		if(!"".equals(notes)){
+			builder.append(NOTES_SPEAK);
+		}
+		if(!bookmarkKeys.isEmpty())
+			builder.append(BOOKMARK_SPEAK);
+		return builder.toString();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/EdgeReferenceMutableTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,88 @@
+/*  
+ 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 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){
+		super();
+		this.edge = edge;
+		this.node = node;
+	}
+	
+	@Override
+	public String toString(){
+		final String and = " and ";
+		StringBuilder b = new StringBuilder();
+		b.append("to ");
+		for(int i=0;i<edge.getNodesNum();i++){
+			DiagramNode n = edge.getNodeAt(i);
+			if(!n.equals(node))
+				b.append(n.getName()).append(and);
+		}
+		// remove the last " and "
+		b.delete(b.length()-and.length(), b.length());
+		b.append(", via ");
+		b.append(edge.getName());
+		super.setUserObject(b.toString());
+		return super.toString();
+	}
+	
+	@Override
+	public String getName(){
+		return toString();
+	}
+	
+	/**
+	 * 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
+	 */
+	@Override
+	public String spokenText(){
+		toString();
+		return super.spokenText();
+	}
+	
+	/**
+	 * Returns the diagram edge that this tree node represents inside the node subtree 
+	 * @return a reference to the actual edge
+	 */
+	public DiagramEdge getEdge(){
+		return edge;
+	}
+	
+	/**
+	 * Returns the node containing this tree node in its subtree. Notice that 
+	 * diagram nodes are DiagrammodelTreeNode as well. 
+	 * @return a reference to the diagram node
+	 */
+	public DiagramNode getNode(){
+		return node;
+	}
+	
+	private DiagramEdge edge;
+	private DiagramNode node;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ElementChangedEvent.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,58 @@
+/*  
+ 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;
+
+/**
+ *  ElementChangedEvent is used to notify the model listeners that an element 
+ *  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) {
+		super(source);
+		this.changeType = changeType;
+		this.element = element;
+	}
+
+	/**
+	 * A String representing the change type. Subclasses of DiagramNode and DiagramEdge 
+	 * can throw their own events by passing as argument a String that describes the change. 
+	 * Such events will have no effect in the model but will be fired to all the registered ChangeEventListener.
+	 *   
+	 * @return a String describing the change type
+	 */
+	public String getChangeType(){
+		return changeType;
+	}
+	
+	/**
+	 * Returns the DiagramElement that has been affected by this change 
+	 * @return the DiagramElement that has been affected by this change
+	 */
+	public DiagramElement getDiagramElement(){
+		return element;
+	}
+	
+	private String changeType;
+	DiagramElement element;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ElementNotifier.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,30 @@
+/*  
+ 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;
+
+/**
+ * An elementNotifier is used by a DiagramElement to make the model aware 
+ * that it has been changed somehow (e.g. it has a new name). 
+ * A Reference to the model's ElementNotifier is set into the DiagramElement
+ * as soon as it's inserted in the model   
+ * 
+ */
+public interface ElementNotifier {
+	void notifyChange(ElementChangedEvent evt);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/MalformedEdgeException.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,36 @@
+/*  
+ 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;
+
+/**
+ * This RuntimeException is thrown when an edge is inserted in the model without being connected to 
+ * any nodes before. 
+ *
+ */
+@SuppressWarnings("serial")
+public class MalformedEdgeException extends RuntimeException {
+
+	public MalformedEdgeException(DiagramEdge edge, String message) {
+		super("Edge inserted into data structure was malformed for this reason:" + message);
+	}
+
+	public MalformedEdgeException(Throwable arg0) {
+		super(arg0);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeProperties.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,527 @@
+/*  
+ 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.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class represents the internal properties of a node. Internal properties can be seen as 
+ * attributes that each single nodes owns and that, in a visual diagram, would normally be displayed inside
+ * or in the neighbourhood of the node itself. This is a very high abstraction of the concept of properties 
+ * of a node as the user can define their own type of properties through the property type definition object 
+ * passed as argument in the constructor.  
+ * An example of properties is <i>attributes</i> and <i>methods</i> of a class diagram in the UML language or just the 
+ * <i>attributes</i> of entities in an ER diagram.   
+ *
+ */
+public final class NodeProperties implements Cloneable {
+	
+	/**
+	 * Creates a diagram element property data structure out of a property type specification. In a UML 
+	 * diagram the NodeProperties for a class node would be constructed passing as arguments a linked hash 
+	 * map containing the strings "attributes" and "properties" and the strings "public", "protected", "private",
+	 * "static" as value for both the keys. "attributes" and "methods" would be the types of the NodeProperties,
+	 *  that is, all the values inserted in the object such as values would fall under either type. For example
+	 *  if a UML diagram class has the methods getX() and getY(), these would be inserted in the NodeProperties
+	 *  object with a type "methods".  
+	 * 
+	 * @param typeDefinition a linked Hash Map holding the properties types as keys
+	 * and the modifiers type definition of each property as values
+	 */
+	
+	public NodeProperties(LinkedHashMap<String,Set<String>> typeDefinition){
+		if(typeDefinition == null)
+			this.typeDefinition = EMPTY_PROPERTY_TYPE_DEFINITION;
+		else
+			this.typeDefinition = typeDefinition;
+		/* create the type collection out of the typeDefinition keyset */ 
+		types = Collections.unmodifiableList(new ArrayList<String>(this.typeDefinition.keySet()));
+		properties = new LinkedHashMap<String,Entry>();
+		for(String s : types){
+			Entry q = new Entry();
+			q.values = new ArrayList<String>(); // property values to be filled by user + modifiers
+			q.indexes = new ArrayList<Set<Integer>>();
+			q.view = null;
+			Set<String> modifierTypeDefinition = this.typeDefinition.get(s);
+			if(modifierTypeDefinition == null)
+				/* null modifiers for this property */
+				q.modifiers = new Modifiers(EMPTY_MODIFIER_TYPE_DEFINITION,q.indexes);
+			else if(modifierTypeDefinition.size() == 0)
+				/* null modifiers for this property */
+				q.modifiers = new Modifiers(EMPTY_MODIFIER_TYPE_DEFINITION,q.indexes);
+			else
+				q.modifiers = new Modifiers(modifierTypeDefinition, q.indexes);
+			properties.put(s, q);
+		}
+	}
+	
+	/**
+	 * Returns the type definition argument of the constructor this object has been created through.
+	 *  
+	 * @return the type definition
+	 */
+	public Map<String,Set<String>> getTypeDefinition(){
+		return typeDefinition;
+	}
+	
+	/**
+	 * Returns the types of properties.
+	 * @return
+	 */
+	public List<String> getTypes(){
+		return types;
+	}
+	
+	/**
+	 * 
+	 * @param types 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.
+	 */
+	public List<String> getValues(String propertyType){
+		Entry e = properties.get(propertyType);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		return new ArrayList<String>(e.values);
+	}
+	
+	/**
+	 * 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. 
+	 * @param type 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 type){
+		Entry e = properties.get(type);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+type);
+		return e.view;
+	}
+	
+	/**
+	 * 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    
+	 * @param type the type of property the view is associated with. 
+	 * @param o an object defined by user
+	 * @see #getView(String)
+	 */
+	public void setView(String type, Object o){
+		Entry e = properties.get(type);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+type);
+		e.view = o;
+	}
+	
+	/**
+	 * 
+	 * 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.
+	 * 
+	 * @param propertyType a property type  
+	 * @return the modifiers of the specified property type or null if either
+	 * the property type is null or it was not listed in the property type definition
+	 * passed to the constructor
+	 */
+	public Modifiers getModifiers(String propertyType){
+		Entry e = properties.get(propertyType);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		return e.modifiers;
+	}
+	
+	/**
+	 * 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
+	 */
+	public void addValue(String propertyType, String propertyValue){
+		Entry e = properties.get(propertyType);
+		/* no such property type exists */
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		e.values.add(propertyValue);
+		e.indexes.add(new LinkedHashSet<Integer>());
+	}
+	
+	/**
+	 * Adds a value of the type passed as argument and sets the modifier indexes for it as for
+	 *  {@link Modifiers#setIndexes(int, Set)} 
+	 * @param propertyType a property type  
+	 * @param propertyValue a property value 
+	 * @param modifierIndexes the modifier set of indexes
+	 * 
+	 * @throws IllegalArgumentException if propertyType 
+	 * is not among the ones in the type definition passed as argument to the constructor 
+	 */
+	public void addValue(String propertyType, String propertyValue, Set<Integer> modifierIndexes){
+		for(Integer i : modifierIndexes)
+			if((i < 0)||(i >= getModifiers(propertyType).getTypes().size())) 
+				throw new ArrayIndexOutOfBoundsException("Index "+ i +
+						" corresponds to no Modifier type (modifierType size = "+
+						getModifiers(propertyType).getTypes().size()+")" );
+		
+		Entry e = properties.get(propertyType);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		e.values.add(propertyValue);
+		e.indexes.add(modifierIndexes);
+	}
+	
+	/**
+	 * 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 
+	 * @throws IllegalArgumentException if propertyType 
+	 * is not among the ones in the type definition passed as argument to the constructor
+	 */
+	public String  removeValue(String propertyType, int valueIndex){
+		Entry e = properties.get(propertyType);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		e.indexes.remove(valueIndex);
+		return e.values.remove(valueIndex);
+	}
+	
+	/**
+	 * 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 
+	 * is not among the ones in the type definition passed as argument to the constructor
+	 */
+	public void setValue(String propertyType, int valueIndex, String newValue){
+		Entry e = properties.get(propertyType);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		e.values.set(valueIndex, newValue);
+	}
+	
+	/**
+	 * 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
+	 */
+	public void clear(String propertyType){
+		Entry e = properties.get(propertyType);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		e.values.clear();
+		e.indexes.clear();
+	}
+	
+	/**
+	 * Removes all the values and modifiers of this object.
+	 */
+	public void clear(){
+		for(String type : types){
+			clear(type);
+		}
+	}
+	
+	/**
+	 * Returns true if this NodeProperties contains no values
+	 * @return true if this NodeProperties contains no values
+	 */
+	public boolean isEmpty(){
+		boolean empty = true;
+		for(String type : types)
+			if(!properties.get(type).values.isEmpty()){
+				empty = false;
+				break;
+			}
+		return empty;
+	}
+	
+	/**
+	 * 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
+	 */
+	public boolean typeIsEmpty(String propertyType){
+		Entry e = properties.get(propertyType);
+		if(e == null)
+			throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+propertyType);
+		return e.values.isEmpty();
+	}
+	
+	/**
+	 * 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
+	 * @return true if this NodeProperties object has no types
+	 */
+	public boolean isNull(){
+		return types.isEmpty();
+	}
+	
+	/**
+	 * 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
+	 * object. Such NodeProperties object though must have the same type and modifier definition of
+	 * the object this method is called on.
+	 * 
+	 * @return a string representation of the values and modifiers of this object
+	 */
+	@Override
+	public String toString(){
+		StringBuilder builder = new StringBuilder();
+		for(String type : types){
+			builder.append(TYPE_ENTRY_SEPARATOR).append(type);
+			int propertyValueIndex = 0;
+			for(String value : properties.get(type).values){
+				builder.append(VALUE_ENTRY_SEPARATOR);
+				builder.append(value);
+				for(int modifierIndex : getModifiers(type).getIndexes(propertyValueIndex)){
+					builder.append(MODIFIER_ENTRY_SEPARATOR);
+					builder.append(modifierIndex);
+				}
+				propertyValueIndex++;	
+			}
+		}
+		return builder.toString();
+	}
+
+	/**
+	 * Fills up the this property according to the string passed as arguments 
+	 * The string must be generated by calling toString on a NodeProeprty instance
+	 * holding the same property types as type, otherwise an IllegalArgumentException 
+	 * is likely to be thrown.   
+	 * 
+	 * @param s the string representation of the property values, such as the one returned
+	 * by toString()
+	 * @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){
+		/* clear up previous values */
+		clear();
+		
+		/* a property entry is a string with the type of the property followed by value           * 
+		 * entries of that type. all value entries begin with the VALUE_SEPARATOR.                *
+		 * a value entry is a property value followed by its modifiers entries, a modifier entry  *
+		 * is a modifier beginning with the MODIFIER_SEPARATOR                                    */
+		String propertyEntries[] = s.split(TYPE_ENTRY_SEPARATOR);
+		for(String propertyEntry : propertyEntries){
+			String[] typeEntries = propertyEntry.split(VALUE_ENTRY_SEPARATOR);
+			String type = typeEntries[0];
+			for(int i=1;i<typeEntries.length;i++){
+				Entry e = properties.get(type);
+				if(e == null)
+					throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+type);
+				String valueEntries[] = typeEntries[i].split(MODIFIER_ENTRY_SEPARATOR);
+				String value = valueEntries[0];
+				LinkedHashSet<Integer> modifiers = new LinkedHashSet<Integer>();
+				for(int j=1;j<valueEntries.length;j++){
+					modifiers.add(Integer.valueOf(valueEntries[j]));
+				}
+				addValue(type,value,modifiers);
+			}
+		}
+	}
+
+	@Override
+	@SuppressWarnings(value = "unchecked")
+	public Object clone(){
+		NodeProperties p = null;
+		try {
+			p = (NodeProperties)super.clone();
+		} catch (CloneNotSupportedException e) {
+			e.printStackTrace();
+		}		
+		p.properties = (LinkedHashMap<String,Entry>)properties.clone();
+		for(String key : p.properties.keySet()){
+			Entry old = p.properties.get(key);
+			Entry q = new Entry();
+			q.values = new  ArrayList<String>(); 
+			q.indexes = new ArrayList<Set<Integer>>();
+			q.view = old.view;
+			if(old.modifiers != null){
+				q.modifiers = new Modifiers(old.modifiers.modifierTypes, q.indexes);
+				for(String modifierType : q.modifiers.getTypes())
+					q.modifiers.setView(modifierType, old.modifiers.getView(modifierType));
+			}
+			p.properties.put(key, q);
+		}
+		return p;
+	}
+	
+	private class Entry{
+		List<String> values;
+		List<Set<Integer>> indexes;
+		Modifiers  modifiers;	
+		Object view;
+ 	}
+	
+	/**
+	 * A modifier is a label peculiar of a certain subset of properties. For example in 
+	 * a UML class diagram one or more methods can be labelled as <i>public</i>, <i>private</i> or <i>protected</i>.
+	 * Had a NodeProperties instance been used to represent the methods of a class node, there would be then 
+	 * one modifier for each label: one for <i>public</i>, one for <i>private</i>, and one for <i>protected</i>. To each modifier
+	 * a view-object can be associated, which describes how these labels would be rendered visually. 
+	 * Following on from the UML example, a view-object would be used with the protected modifier
+	 * to hold the information that the properties which are assigned such a modifier must be prefixed
+	 * with the '#' sign.   
+	 */
+	public class Modifiers{		
+		private Modifiers(Set<String> modifierTypes, List<Set<Integer>> indexes){
+			views = new LinkedHashMap<String,Object>();
+			indexesRef = indexes;
+			this.modifierTypes = Collections.unmodifiableList(new ArrayList<String>(modifierTypes));
+			for(String modifierType : modifierTypes){
+				views.put(modifierType,null);
+			}			
+		}
+		
+		/* only used by NodeProperties.clone() method */
+		private Modifiers(List<String> modifierTypes, List<Set<Integer>> indexes){
+			views = new LinkedHashMap<String,Object>();
+			indexesRef = indexes;
+			this.modifierTypes = modifierTypes;
+			for(String modifierType : modifierTypes){
+				views.put(modifierType,null);
+			}
+		}
+		
+		/**
+		 * 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)
+		 */
+		public List<String> getTypes(){
+			return modifierTypes;
+		}
+		
+		/**
+		 * 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
+		 * @return the View object or null if non has been set previously 
+		 */
+		public Object getView(String modifierType){
+			return views.get(modifierType);
+		}
+		
+		/**
+		 * 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 o an object defined by user
+		 * @see #getView(String)
+		 * @throws IllegalArgumentException if modifierType 
+	     * is not among the ones in the type definition passed as argument to the constructor
+		 */
+		public void setView(String modifierType, Object view){
+			if(!views.containsKey(modifierType))
+				throw new IllegalArgumentException(ILLEGAL_TYPE_MSG+modifierType);
+			views.put(modifierType,view);
+		}
+		
+		/**
+		 * Returns the modifier indexes for the specified property value. Each property type can be associated 
+		 * with one or more modifier type and each property value can be assigned one or more modifier. The 
+		 * integer set returned by this method tells the client code to which modifiers the property value at the index 
+		 * passed as argument is assigned. The returned indexes refer to the modifier list returned by {@link #getTypes()}
+		 *    
+		 * @param propertyValueIndex
+		 * @return a set of indexes of the modifier types the property value at the index passed as agument
+		 * is assigned to
+		 */
+		public Set<Integer> getIndexes(int propertyValueIndex){
+			Set<Integer> set = indexesRef.get(propertyValueIndex);
+			return (new LinkedHashSet<Integer>(set));
+		}
+		
+		/**
+		 * 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
+		 * or equal to the size of the modifier type list returned by {@link #getTypes()}. The same exception is also thrown 
+		 * if propertyValueIndex is lower than 0 or greater or equal to the property values list returned by {@link NodeProperties#getValues(String)}
+		 * passing as argument the same property type passed to {@link NodeProperties#getModifiers(String)} to obtain
+		 * this Modifiers instance. 
+		 * @see #getIndexes(int)
+		 */
+		public void setIndexes(int propertyValueIndex, Set<Integer> modifierIndexes){
+			for(Integer i : modifierIndexes)
+				if((i < 0)||(i >= getTypes().size())) 
+					throw new ArrayIndexOutOfBoundsException("Index "+ i + " corresponds to no Modifier Type (modifierType size = "+getTypes().size()+")" );
+
+			Set<Integer> m = indexesRef.get(propertyValueIndex);
+			m.clear();
+			m.addAll(modifierIndexes);
+		}
+		
+		/**
+		 * 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){
+			Set<Integer> m = indexesRef.get(propertyValueIndex);
+			m.clear();
+		}
+		
+		/**
+		 * 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
+		 * @return true if this Modifiers object has no types
+		 */
+		public boolean isNull(){
+			return modifierTypes.isEmpty();
+		}
+		
+		private LinkedHashMap<String,Object> views;
+		private final List<String> modifierTypes;
+		private List<Set<Integer>> indexesRef;
+	}
+	
+	/* for each property (key) I associate a Couple (value) made out of  *
+	 * a list of the property values plus a list of possible modifiers 	 */
+	private LinkedHashMap<String,Entry> properties;
+	private final List<String> types;
+	private Map<String,Set<String>> typeDefinition;
+	 
+	private static final LinkedHashMap<String,Set<String>> EMPTY_PROPERTY_TYPE_DEFINITION = new LinkedHashMap<String,Set<String>>();
+	private static final Set<String> EMPTY_MODIFIER_TYPE_DEFINITION = Collections.emptySet();
+	private static final String ILLEGAL_TYPE_MSG = "argument must be in type definition list: ";
+	/**
+	 * 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   
+	 */
+	public static final NodeProperties NULL_PROPERTIES = new NodeProperties(EMPTY_PROPERTY_TYPE_DEFINITION);
+	
+	private final static String TYPE_ENTRY_SEPARATOR = "\n:";
+	private final static String VALUE_ENTRY_SEPARATOR = "\n;";
+	private final static String MODIFIER_ENTRY_SEPARATOR = "\n,";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeReferenceMutableTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,97 @@
+/*  
+ 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 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){
+		super();
+		this.node = node;
+		this.edge = edge;
+	}
+	
+	
+	@Override
+	public String toString(){
+		StringBuilder b = new StringBuilder();
+		if(edge.getEndDescription(node) != null){
+			b.append(' ');
+			b.append(edge.getEndDescription(node));
+			b.append(' ');
+		}
+		b.append(node.getName());
+		b.append(' ');
+		if(edge.getEndLabel(node) != null){
+			b.append(edge.getEndLabel(node));
+		}
+		/* set the user object so that superclass toString can be called, which 
+		 * decorates the string with notes and bookmarks
+		 */
+		setUserObject(b.toString());
+		return super.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
+	public String getName(){
+		return node.getName();
+	}
+	
+	/**
+	 * 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
+	public String spokenText(){
+		toString();
+		return super.spokenText();
+	}
+	
+	/**
+	 * 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(){
+		return edge;
+	}
+	
+	/**
+	 * Returns the diagram node that this tree node represents inside the edge subtree 
+	 * @return a reference to the actual diagram node
+	 */
+	public DiagramNode getNode(){
+		return node;
+	}
+
+	private DiagramNode node;
+	private DiagramEdge edge;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyMutableTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,64 @@
+/*  
+ 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;
+
+/**
+ * This class represent node properties in the diagram tree representation. In the tree layout they are placed 
+ * in the DiagramNode subtree. Note that a DiagramNode is also a DiagramModelTreeNode.
+ * @see NodeProperties 
+ */
+@SuppressWarnings("serial")
+public class PropertyMutableTreeNode extends DiagramModelTreeNode {
+	public PropertyMutableTreeNode(){
+		super();
+	}
+	
+	
+	public PropertyMutableTreeNode(Object userObject) {
+		super(userObject);
+		modifiersString = "";
+	}
+	
+	/**
+	 * Sets a string to show in the tree that the NodeProperties value this tree node refers to 
+	 * has been assigned one or more modifiers. 
+	 * 
+	 * @param s the modifier string
+	 */
+	public void setModifierString(String s){
+		modifiersString = s;
+	}
+	
+	@Override
+	public String toString(){
+		return modifiersString + super.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 toString(). 
+	 * @return a String suitable for text to speech synthesis
+	 */
+	@Override
+	public String spokenText(){
+		return modifiersString + super.spokenText();
+	}
+	
+	private String modifiersString;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/PropertyTypeMutableTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,101 @@
+/*  
+ 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.List;
+import java.util.Set;
+
+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.
+ *  
+ * @see PropertyMutableTreeNode
+ *
+ */
+@SuppressWarnings("serial")
+public class PropertyTypeMutableTreeNode extends DiagramModelTreeNode {
+	public PropertyTypeMutableTreeNode(String type, DiagramNode n){
+		setUserObject(type);
+		node = n;
+	}
+	
+	public String getType(){
+		return getName();
+	}
+	
+	public DiagramNode getNode(){
+		return node;
+	}
+	
+	@Override
+	public String toString(){
+		StringBuilder builder = new StringBuilder(super.toString());
+		builder.append(" (").append(getChildCount()).append(")");
+		return builder.toString();
+	}
+	
+	/**
+	 * 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
+	 */
+	@Override
+	public String spokenText(){
+		StringBuilder builder = new StringBuilder(getName());
+		builder.append(", ");
+		builder.append(getChildCount() == 0 ? "empty" : getChildCount());
+		if(!"".equals(notes)){
+			builder.append(NOTES_SPEAK);
+		}
+		if(!bookmarkKeys.isEmpty())
+			builder.append(BOOKMARK_SPEAK);
+		return builder.toString();
+	}
+
+	/* creates and/or set the child treeNodes of this type with the property values */
+	public void setValues(List<String> values, Modifiers modifiers){
+		int diff = getChildCount() - values.size();
+		if(diff > 0){
+			for(int i=0;i<diff;i++)
+				remove(getChildCount()-1);
+		}else if(diff < 0){
+			for(int i=0; i<-diff;i++)
+				add(new PropertyMutableTreeNode());
+		}
+		
+		PropertyMutableTreeNode child;
+		for(int i=0; i<getChildCount();i++){
+			StringBuilder builder = new StringBuilder();
+			if(modifiers != null){
+				Set<Integer> indexes = modifiers.getIndexes(i);
+				for(int index : indexes)
+					builder.append(modifiers.getTypes().get(index)).append(' ');
+			}
+			child = (PropertyMutableTreeNode)getChildAt(i);
+			child.setUserObject(values.get(i));
+			child.setModifierString(builder.toString());
+		}
+	}
+	
+	DiagramNode node;
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TreeModel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,133 @@
+/*  
+ 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.Set;
+
+import javax.swing.event.ChangeListener;
+
+/**
+ * 
+ * Represents the tree side of a DiagramModel instance. 
+ *
+ * @param <N> a type extending DiagramNode
+ * @param <E> a type extending DiagramEdge
+ */
+public interface TreeModel<N extends DiagramNode, E extends DiagramEdge> extends javax.swing.tree.TreeModel {
+	
+	/**
+	 * insert a DiagramNode into the diagram model
+	 * 
+	 * @param treeNode the DiagramNode to be inserted in the collection
+	 * @return true if the model changed as a result of the call
+	 */
+	boolean insertTreeNode(N treeNode);
+	
+	/**
+	 * insert a DiagramEdge into the diagram model
+	 * 
+	 * @param treeNode the DiagramEdge to be inserted in the collection
+	 * @return true if the model changed as a result of the call
+	 */
+	boolean insertTreeNode(E treeNode);
+	
+	/**
+	 * remove a DiagramElement from the model 
+	 * 
+	 * @param treeNode the diagramElement to be removed 
+	 * @return true if the model changed as a result of the call
+	 */
+	boolean takeTreeNodeOut(DiagramElement treeNode);
+	
+	/**
+	 * 
+	 * Add a bookmark for the specified tree node in the internal collection
+	 * 
+	 * @param bookmark a bookmark 
+	 * @param treeNode the tree node to be bookmarked
+	 * @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);
+	
+	/**
+	 * 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);
+	
+	/**
+	 * Returns the list of all the bookmarks of this tree model
+	 * @return the list of all the bookmarks
+	 */
+	Set<String> getBookmarks();
+	
+	/**
+	 * Remove the bookmark from the bookmark internal collection
+	 * 
+	 * @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);
+	
+	/**
+	 * Set the notes for the specified tree node. Passing an empty string as notes 
+	 * means actually to remove the notes from the tree node. 
+	 *  
+	 * @param treeNode the tree node to be noted 
+	 * @param notes the notes to be assigned to the tree node 
+	 */
+	void setNotes(DiagramModelTreeNode treeNode, String notes);
+	
+	/**
+	 * Add a change listener to the model. 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  
+	 */
+	void addChangeListener(ChangeListener l);
+	
+	/**
+	 * Removes a change listener from the model. 
+	 * @param l a ChangeListener to remove from the model
+	 */
+	void removeChangeListener(ChangeListener l);
+	
+	/**
+	 * Returns true if the model has been modified
+	 * @return true if the model has been modified
+	 */
+	boolean isModified();
+	
+	/**
+	 * Sets the model as unmodified. This entails that {@link #isModified()} will return 
+	 * false unless the model doesn't get modified again. After this call a new modification
+	 * of the model would trigger the associated change listeners again. 
+	 */
+	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
+	 */
+	Object getMonitor();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TypeMutableTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,70 @@
+/*  
+ 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;
+
+/**
+ *
+ * This class is a DiagramModelTreeNode representing a type of a diagram element (node or edge)
+ * in the diagram tree layout the elements will be children of this tree nodes.  
+ *
+ */
+@SuppressWarnings("serial")
+public class TypeMutableTreeNode extends DiagramModelTreeNode {
+
+	public TypeMutableTreeNode(DiagramElement element) {
+		super(element.getType());
+		this.prototype = element;
+	}
+
+	/**
+	 * Returns a prototype diagram element which can be cloned to create other diagram elements
+	 * of this type.
+	 * @return
+	 */
+	public DiagramElement getPrototype(){
+		return (DiagramElement)prototype.clone();
+	}
+	
+	@Override
+	public String toString(){
+		StringBuilder builder = new StringBuilder(super.toString());
+		builder.append(" (").append(getChildCount()).append(")");
+		return builder.toString();
+	}
+	
+	/**
+	 * 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
+	 */
+	@Override
+	public String spokenText(){
+		StringBuilder builder = new StringBuilder(getName());
+		builder.append(", ");
+		builder.append(getChildCount() == 0 ? "empty" : getChildCount());
+		if(!"".equals(notes)){
+			builder.append(NOTES_SPEAK);
+		}
+		if(!bookmarkKeys.isEmpty())
+			builder.append(BOOKMARK_SPEAK);
+		return builder.toString();
+	}
+	
+	DiagramElement prototype;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Diagram.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,264 @@
+/*  
+ 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.geom.Point2D;
+import java.util.Set;
+
+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.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PrototypePersistenceDelegate;
+
+/**
+ * This 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 {
+	
+	public static Diagram newInstance(String name, Node[] nodes, Edge[] edges, PrototypePersistenceDelegate prototypePersistenceDelegate){
+		return new LocalDiagram(name,nodes,edges,prototypePersistenceDelegate);
+	}
+	
+	public abstract String getName();
+
+	public abstract void setName(String name);
+
+	public abstract Node[] getNodePrototypes();
+
+	public abstract Edge[] getEdgePrototypes();
+
+	public abstract TreeModel<Node,Edge> getTreeModel();
+
+	public abstract CollectionModel<Node,Edge> getCollectionModel();
+
+	public abstract DiagramModelUpdater getModelUpdater();
+	
+	public abstract String getLabel();
+	
+	public abstract PrototypePersistenceDelegate getPrototypePersistenceDelegate();
+
+	@Override
+	public Object clone(){
+		try {
+			return super.clone();
+		} catch (CloneNotSupportedException e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
+	private static class LocalDiagram extends Diagram {
+
+		private LocalDiagram(String name, Node[] nodes, Edge[] edges,PrototypePersistenceDelegate prototypePersistenceDelegate){
+			this.name = name;
+			this.nodes = nodes;
+			this.edges = edges;
+			this.prototypePersistenceDelegate = prototypePersistenceDelegate;
+			diagramModel = new DiagramModel<Node,Edge>(nodes,edges);
+			innerModelUpdater = new InnerModelUpdater();
+		}
+
+		@Override
+		public String getName(){
+			return name;
+		}
+
+		@Override
+		public void setName(String name){
+			this.name = name;
+		}
+
+		@Override
+		public Node[] getNodePrototypes(){
+			return nodes;
+		}
+
+		@Override
+		public Edge[] getEdgePrototypes(){
+			return edges;
+		}
+
+		@Override
+		public TreeModel<Node,Edge> getTreeModel(){
+			return diagramModel.getTreeModel();
+		}
+
+		@Override
+		public CollectionModel<Node,Edge> getCollectionModel(){
+			return diagramModel.getDiagramCollection();
+		}
+
+		@Override
+		public String getLabel(){
+			return name;
+		}
+		
+		@Override
+		public DiagramModelUpdater getModelUpdater(){
+			return innerModelUpdater;
+		}
+		
+		@Override
+		public String toString(){
+			return name;
+		}
+		
+		@Override
+		public PrototypePersistenceDelegate getPrototypePersistenceDelegate(){
+			return prototypePersistenceDelegate;
+		}
+		
+		@Override
+		public Object clone(){
+			LocalDiagram clone = (LocalDiagram)super.clone();
+			clone.name = getName();
+			clone.nodes = getNodePrototypes();
+			clone.edges = getEdgePrototypes();
+			/* constructor with no args makes just a dummy wrapper */
+			clone.diagramModel = new DiagramModel<Node,Edge>(nodes,edges);
+			clone.innerModelUpdater = clone.new InnerModelUpdater();
+			return clone;
+		}
+
+		private DiagramModel<Node,Edge> diagramModel;
+		private InnerModelUpdater innerModelUpdater;
+		private PrototypePersistenceDelegate prototypePersistenceDelegate;
+		private String name;
+		private Node[] nodes;
+		private Edge[] edges;
+
+		private class InnerModelUpdater implements DiagramModelUpdater {
+
+			@Override
+			public boolean getLock(DiagramModelTreeNode treeNode, Lock lock) {
+				/* 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) {}
+
+			@Override
+			public void insertInCollection(DiagramElement element) {
+				if(element instanceof Node)
+					diagramModel.getDiagramCollection().insert((Node)element);
+				else
+					diagramModel.getDiagramCollection().insert((Edge)element);
+			}
+
+			@Override
+			public void insertInTree(DiagramElement element) {
+				if(element instanceof Node)
+					diagramModel.getTreeModel().insertTreeNode((Node)element);
+				else
+					diagramModel.getTreeModel().insertTreeNode((Edge)element);
+			}
+
+			@Override
+			public void takeOutFromCollection(DiagramElement element) {
+				diagramModel.getDiagramCollection().takeOut(element);
+			}
+
+			@Override
+			public void takeOutFromTree(DiagramElement element) {
+				diagramModel.getDiagramCollection().takeOut(element);
+			}
+
+			@Override
+			public void setName(DiagramElement element, String name) {
+				element.setName(name);
+			}
+
+			@Override
+			public void setNotes(DiagramModelTreeNode treeNode, String notes) {
+				diagramModel.getTreeModel().setNotes(treeNode, notes);
+			}
+
+			@Override
+			public void setProperty(Node node, String type, int index,
+					String value) {
+				node.setProperty(type, index, value);
+			}
+
+			@Override
+			public void setProperties(Node node, NodeProperties properties) {
+				node.setProperties(properties);
+			}
+
+			@Override
+			public void clearProperties(Node node) {
+				node.clearProperties();
+			}
+
+			@Override
+			public void addProperty(Node node, String type, String value) {
+				node.addProperty(type, value);
+			}
+
+			@Override
+			public void removeProperty(Node node, String type, int index) {
+				node.removeProperty(type, index);
+			}
+
+			@Override
+			public void setModifiers(Node node, String type, int index,
+					Set<Integer> modifiers) {
+				node.setModifierIndexes(type, index, modifiers);				
+			}
+
+			@Override
+			public void setEndLabel(Edge edge, Node node, String label) {
+				edge.setEndLabel(node, label);
+			}
+
+			@Override
+			public void setEndDescription(Edge edge, Node node,
+					int index) {
+				edge.setEndDescription(node, index);
+			}
+
+			@Override
+			public void translate(GraphElement ge, Point2D p, double x, double y) {
+				ge.translate(p, x, y);
+			}
+
+			@Override
+			public void startMove(GraphElement ge, Point2D p) {
+				ge.startMove(p);
+			}
+
+			@Override
+			public void bend(Edge edge, Point2D p) {
+				edge.bend(p);
+			}
+
+			@Override
+			public void stopMove(GraphElement ge) {
+				ge.stopMove();
+			}
+		}
+	}
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramModelUpdater.java	Fri Dec 16 17:35:51 2011 +0000
@@ -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.gui;
+
+import java.awt.geom.Point2D;
+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.NodeProperties;
+
+/**
+ * 
+ * The DiagramModelUpdater class is used to make changes to the diagram model. The reason why 
+ * changes are not made directly to the model is allowing the network-local diagram interchangeability. 
+ * A NetDiagram differs from a local 
+ * diagram only by its DiagramModelUpdater implementation. The rest of the operations are 
+ * performed through the delegate local diagram which is passed as argument to the constructor.    
+ * In this way a local diagram can be easily turned into a network diagram and vice versa.
+ *
+ * @see NetDiagram
+ */
+public interface DiagramModelUpdater {
+	
+	public boolean getLock(DiagramModelTreeNode treeNode, Lock lock);
+	
+	public void yieldLock(DiagramModelTreeNode treeNode, Lock lock);
+	
+	public void insertInCollection(DiagramElement element);
+	
+	public void insertInTree(DiagramElement element);
+	
+	public void takeOutFromCollection(DiagramElement element);
+	
+	public void takeOutFromTree(DiagramElement element);
+		
+	public void setName(DiagramElement element, String name);
+	
+	public void setProperty(Node node, String type, int index, String value);
+	
+	public void setProperties(Node node, NodeProperties properties);
+	
+	public void clearProperties(Node node);
+	
+	public void setNotes(DiagramModelTreeNode treeNode, String notes);
+	
+	public void addProperty(Node node, String type, String value);
+	
+	public void removeProperty(Node node, String type, int index);
+	
+	public void setModifiers(Node node, String type, int index, Set<Integer> modifiers);
+	
+	public void setEndLabel(Edge edge, Node node, String label);
+	
+	public void setEndDescription(Edge edge, Node node, int i);// String description);
+	
+	public void translate(GraphElement ge, Point2D p, double x, double y);
+	
+	public void startMove(GraphElement ge, Point2D p);
+	
+	public void bend(Edge edge, Point2D p);
+	
+	public void stopMove(GraphElement ge);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramPanel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,132 @@
+/*  
+ 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/)
+
+ 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.BorderLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * It's the panel which displays a diagram. It contains a {@link GraphPanel}, a {@link DiagramTree} 
+ * and a {@link GraphToolbar} 
+ *
+ */
+@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;
+   }
+
+   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();
+   }
+
+   private Diagram diagram;
+   private GraphPanel graphPanel;
+   private DiagramTree tree;
+   private GraphToolbar toolbar;
+   private String filePath;
+   private ChangeListener modelChangeListener;
+   private EditorTabbedPane tabbedPane;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,723 @@
+/*  
+ 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.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.JOptionPane;
+import javax.swing.JTree;
+import javax.swing.KeyStroke;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+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.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.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.Narrator;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
+import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
+
+@SuppressWarnings("serial")
+public class DiagramTree extends JTree {
+	public DiagramTree(Diagram diagram){
+		super(diagram.getTreeModel());
+		this.diagram = diagram;
+		resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+		
+		TreePath rootPath = new TreePath((DiagramModelTreeNode)diagram.getTreeModel().getRoot());
+		setSelectionPath(rootPath);
+		collapsePath(rootPath);
+		selectedNodes = new ArrayList<Node>();
+		setEditable(false);
+	    getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+	    overwriteTreeKeystrokes();
+	    this.addTreeSelectionListener(new TreeSelectionListener(){
+	    	@Override
+	    	public void valueChanged(TreeSelectionEvent evt) {
+	    		if(treeSelectionListenerGateOpen){
+	    			final DiagramModelTreeNode treeNode = (DiagramModelTreeNode)evt.getPath().getLastPathComponent();
+	    			if(treeNode instanceof DiagramElement){
+	    				SoundFactory.getInstance().play(((DiagramElement)treeNode).getSound(), new PlayerListener(){
+	    					@Override
+	    					public void playEnded() {
+	    						NarratorFactory.getInstance().speak(treeNode.spokenText());
+	    					}
+	    				});
+	    			}else{
+	    				NarratorFactory.getInstance().speak(treeNode.spokenText());
+	    			}
+	    		}
+	    	}
+		});
+	    /* don't use the swing focus system as we provide one on our own */
+	    setFocusTraversalKeysEnabled(false);
+	    getAccessibleContext().setAccessibleName("");
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	public TreeModel<Node,Edge> getModel(){
+		return (TreeModel<Node,Edge>)super.getModel();
+	}
+	
+	public void setModel(TreeModel<Node,Edge> newModel){
+		DiagramModelTreeNode selectedTreeNode = (DiagramModelTreeNode)getSelectionPath().getLastPathComponent();
+		super.setModel(newModel);
+		collapseRow(0);
+		setSelectionPath(new TreePath(selectedTreeNode.getPath()));
+	}
+	
+	public void setDiagram(Diagram diagram){
+		this.diagram = diagram;
+		setModel(diagram.getTreeModel());
+	}
+	
+	public void selectNode(final Node n){
+		selectedNodes.add(n);
+		treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
+		
+		SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
+			@Override
+			public void playEnded() {
+				NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.node_selected"),n.spokenText()));
+			}
+		});
+		InteractionLog.log(INTERACTIONLOG_SOURCE,"node selected for edge",n.getName());
+	}
+	
+	public void unselectNode(final Node n){
+		selectedNodes.remove(n);
+		treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
+		
+		SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
+			@Override
+			public void playEnded() {
+				NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.node_unselected"),n.spokenText()));
+			}
+		});
+		InteractionLog.log(INTERACTIONLOG_SOURCE,"node unselected for edge",DiagramElement.toLogString(n));
+	}
+	
+	public DiagramNode[] getSelectedNodes(){
+		DiagramNode[] array = new DiagramNode[selectedNodes.size()]; 
+		return selectedNodes.toArray(array);
+	}
+	
+	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);
+		}
+	}
+	
+	public String currentPathSpeech(){
+		TreePath path =	getSelectionPath();
+		DiagramModelTreeNode selectedPathTreeNode = (DiagramModelTreeNode)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());
+		else
+			return selectedPathTreeNode.spokenText();
+	}
+	
+	/**
+	 * this method changes the selected tree node from an edge/node reference
+	 * to the related edge/node itself
+	 */
+	public void jump(JumpTo jumpTo){
+		final Narrator narrator = NarratorFactory.getInstance();
+		TreePath oldPath;
+		treeSelectionListenerGateOpen = false;
+		switch(jumpTo){
+		case REFERENCE :
+			oldPath = getSelectionPath();
+			DiagramModelTreeNode selectedTreeNode = (DiagramModelTreeNode)oldPath.getLastPathComponent();
+			if(selectedTreeNode instanceof NodeReferenceMutableTreeNode){
+				final Node n = (Node)((NodeReferenceMutableTreeNode)selectedTreeNode).getNode();
+				setSelectionPath(new TreePath(n.getPath()));
+				SoundFactory.getInstance().setPlayerListener(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(){
+					   @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]);
+			break;
+		case ROOT :
+			final DiagramModelTreeNode from =(DiagramModelTreeNode)getSelectionPath().getLastPathComponent();
+			setSelectionRow(0);
+			collapseAll(from,from.getRoot());
+			SoundFactory.getInstance().setPlayerListener(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 SELECTED_TYPE :
+			DiagramModelTreeNode root = (DiagramModelTreeNode)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
+			oldPath = getSelectionPath();
+			/* initial value is the type node whose branch node is currently selected */
+			/* it is set as the first choice in the selection dialog */
+			Object initialValue;
+			if(oldPath.getPath().length < 2)
+				initialValue = types[0];
+			else
+				initialValue = oldPath.getPathComponent(1);//type tree  node
+			/* the selection from the OptionPane is the stering returned by getName() */
+			InteractionLog.log(INTERACTIONLOG_SOURCE,"open select type to jump dialog","");
+			final String selectedValue = (String)SpeechOptionPane.showSelectionDialog(
+					SpeechOptionPane.getFrameForComponent(this),
+					"select type to jump to", 
+					types, 
+					initialValue);
+			if(selectedValue == null){
+				/* it speaks anyway as we set up the playerListener in the EditorFrame class. No need to use narrator then */
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				InteractionLog.log(INTERACTIONLOG_SOURCE,"cancel select type to jump dialog","");
+				treeSelectionListenerGateOpen = true;
+				return;
+			}
+			/* we search in the root which type tree node has getName() equal to the selected one */
+			TypeMutableTreeNode typeNode = null;
+			for(int i = 0; i< root.getChildCount(); i++){
+				TypeMutableTreeNode temp = (TypeMutableTreeNode)root.getChildAt(i);
+				if(temp.getName().equals(selectedValue)){
+					typeNode = temp;
+					break;
+				}
+			}
+			setSelectionPath(new TreePath(typeNode.getPath()));
+			if(oldPath.getPath().length >= 2)
+				collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(), (DiagramModelTreeNode)initialValue);
+			SoundFactory.getInstance().setPlayerListener(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(); 
+			
+			if(treeModel.getBookmarks().size() == 0){
+				SoundFactory.getInstance().play(SoundEvent.ERROR ,new PlayerListener(){
+					   @Override
+					   public void playEnded() {
+						   narrator.speak("speech.no_bookmarks");
+					   }
+				   });
+				InteractionLog.log(INTERACTIONLOG_SOURCE,"no bookmarks available","");
+				treeSelectionListenerGateOpen = true;
+				return;
+			}
+
+			String[] bookmarkArray = new String[treeModel.getBookmarks().size()];
+			bookmarkArray = treeModel.getBookmarks().toArray(bookmarkArray);
+
+			InteractionLog.log(INTERACTIONLOG_SOURCE,"open select bookmark dialog","");
+			String bookmark = (String)SpeechOptionPane.showSelectionDialog(
+					JOptionPane.getFrameForComponent(this), 
+					"Select bookmark",
+					bookmarkArray,
+					bookmarkArray[0]
+			);
+
+			if(bookmark != null){
+				oldPath = getSelectionPath();
+				DiagramModelTreeNode treeNode = treeModel.getBookmarkedTreeNode(bookmark);
+				collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(), (DiagramModelTreeNode)treeModel.getRoot());
+				setSelectionPath(new TreePath(treeNode.getPath()));
+				final String currentPathSpeech = currentPathSpeech();
+				SoundFactory.getInstance().setPlayerListener(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 */
+				SoundFactory.getInstance().play(SoundEvent.CANCEL);
+				InteractionLog.log(INTERACTIONLOG_SOURCE,"cancel select bookmark dialog","");
+				treeSelectionListenerGateOpen = true;
+				return;
+			}
+			break;
+			
+		}
+		InteractionLog.log(INTERACTIONLOG_SOURCE,"jumped to "+jumpTo.toString(),((DiagramModelTreeNode)getSelectionPath().getLastPathComponent()).getName());
+		SoundFactory.getInstance().play(SoundEvent.JUMP);
+		treeSelectionListenerGateOpen = true;
+	}
+	
+	public void jumpTo(final DiagramElement de){
+		TreePath oldPath = getSelectionPath();
+		collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(),de);
+		setSelectionPath(new TreePath(de.getPath()));
+		treeSelectionListenerGateOpen = false;
+		SoundFactory.getInstance().play( SoundEvent.JUMP, new PlayerListener(){
+			@Override
+			public void playEnded() {
+				NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.jump"),de.spokenText()));
+			}
+		});
+		treeSelectionListenerGateOpen = true;
+	}
+	
+	/* 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;
+		while(currentNode.getParent() != null && currentNode != to){
+			currentNode = currentNode.getParent();
+			collapsePath(new TreePath(currentNode.getPath()));
+		}
+	}
+	
+	@Override
+	protected  void	processMouseEvent(MouseEvent e){
+		//do nothing as the tree does not have to be editable with mouse
+	}
+	
+	private void overwriteTreeKeystrokes() {
+		   /* overwrite the keys. up and down arrow are overwritten so that it loops when the top and the  */
+		   /* bottom are reached rather than getting stuck                               */ 
+		   
+		   /* Overwrite keystrokes up,down,left,right arrows and space, shift, ctrl */
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,0),"down");
+		   getActionMap().put("down", new AbstractAction(){
+				@Override
+				public void actionPerformed(ActionEvent evt) {
+					DiagramModelTreeNode treeNode = (DiagramModelTreeNode)getLastSelectedPathComponent();
+					/* look if we've got a sibling node after (we are not at the bottom) */
+					DiagramModelTreeNode nextTreeNode = treeNode.getNextSibling(); 
+					SoundEvent loop = null;
+					if(nextTreeNode == null){
+						DiagramModelTreeNode 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();
+						loop = SoundEvent.LIST_BOTTOM_REACHED;
+					}
+					treeSelectionListenerGateOpen = false;
+					setSelectionPath(new TreePath(nextTreeNode.getPath()));
+					final InputStream finalSound  = getTreeNodeSound(nextTreeNode);
+					final String currentPathSpeech = currentPathSpeech();
+					SoundFactory.getInstance().play(loop, new PlayerListener(){
+						public void playEnded() {
+							SoundFactory.getInstance().play(finalSound);
+							NarratorFactory.getInstance().speak(currentPathSpeech);
+						}
+					});
+					treeSelectionListenerGateOpen = true;
+					InteractionLog.log(INTERACTIONLOG_SOURCE,"move down",nextTreeNode.toString());
+				}});
+		   
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP,0),"up");
+		   getActionMap().put("up", new AbstractAction(){
+				@Override
+				public void actionPerformed(ActionEvent evt) {
+					DiagramModelTreeNode treeNode = (DiagramModelTreeNode)getLastSelectedPathComponent();
+					DiagramModelTreeNode previousTreeNode = treeNode.getPreviousSibling();
+					SoundEvent loop = null;
+					if(previousTreeNode == null){
+						DiagramModelTreeNode parent = treeNode.getParent();
+						if(parent == null) /* root node */
+							previousTreeNode = treeNode;
+						else 
+							previousTreeNode = (DiagramModelTreeNode)parent.getLastChild();
+						loop = SoundEvent.LIST_TOP_REACHED;
+					}
+					treeSelectionListenerGateOpen = false;
+					setSelectionPath(new TreePath(previousTreeNode.getPath()));
+					final InputStream finalSound  = getTreeNodeSound(previousTreeNode);
+					final String currentPathSpeech = currentPathSpeech();
+					SoundFactory.getInstance().play(loop, new PlayerListener(){
+						public void playEnded() {
+							SoundFactory.getInstance().play(finalSound);
+							NarratorFactory.getInstance().speak(currentPathSpeech);
+						}
+					});
+					treeSelectionListenerGateOpen = true;
+					InteractionLog.log(INTERACTIONLOG_SOURCE,"move up",previousTreeNode.toString());
+				}});
+		   
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0),"right");
+		   getActionMap().put("right", new AbstractAction(){
+			   @Override
+			   public void actionPerformed(ActionEvent evt) {
+				   TreePath path = getSelectionPath();
+				   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent();
+				   if(treeNode.isLeaf()){
+					   notifyBorderReached(treeNode);
+					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move right","border reached");
+				   }
+				   else{
+					   treeSelectionListenerGateOpen = false;
+					   expandPath(path);
+					   setSelectionPath(new TreePath(((DiagramModelTreeNode)treeNode.getFirstChild()).getPath()));
+					   final String currentPathSpeech = currentPathSpeech();
+					   SoundFactory.getInstance().play(SoundEvent.TREE_NODE_EXPAND,new PlayerListener(){
+						@Override
+						public void playEnded() {
+							NarratorFactory.getInstance().speak(currentPathSpeech);
+						}
+					   });
+					   treeSelectionListenerGateOpen = true;
+					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move right",((DiagramModelTreeNode)treeNode.getFirstChild()).toString());
+				   }
+			   }
+		   });
+		   
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0),"left");
+		   getActionMap().put("left", new AbstractAction(){
+			   @Override
+			   public void actionPerformed(ActionEvent evt) {
+				   TreePath path = getSelectionPath();
+				   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent();
+				   DiagramModelTreeNode parent = treeNode.getParent();
+				   if(parent == null){/* root node */
+					   notifyBorderReached(treeNode);
+					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move left","border reached");
+				   }
+				   else{
+					   treeSelectionListenerGateOpen = false;
+					   TreePath newPath = new TreePath(((DiagramModelTreeNode)parent).getPath());
+					   setSelectionPath(newPath);
+					   collapsePath(newPath);
+					   final String currentPathSpeech = currentPathSpeech(); 
+					   SoundFactory.getInstance().play(SoundEvent.TREE_NODE_COLLAPSE,new PlayerListener(){
+							@Override
+							public void playEnded() {
+								NarratorFactory.getInstance().speak(currentPathSpeech);
+							}
+					   });
+					   InteractionLog.log(INTERACTIONLOG_SOURCE,"move left",((DiagramModelTreeNode)parent).toString());
+					   treeSelectionListenerGateOpen = true;
+				   }
+			   }
+		   });
+		   
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0),"space");
+		   getActionMap().put("space",new AbstractAction(){
+			   @Override
+			   public void actionPerformed(ActionEvent arg0) {
+				   NarratorFactory.getInstance().speak(currentPathSpeech());
+				   InteractionLog.log(INTERACTIONLOG_SOURCE,"info requested","");
+			   }
+		   });
+		   
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,InputEvent.CTRL_DOWN_MASK),"ctrlspace");
+		   getActionMap().put("ctrlspace",new AbstractAction(){
+			   @Override
+			   public void actionPerformed(ActionEvent arg0) {
+				  /*//this code snippet reads out the whole path from the root to the selected node
+				   * StringBuilder builder = new StringBuilder();
+				   * TreePath path =	getSelectionPath();
+				   * for(Object o : path.getPath()){
+				   * 		builder.append(((DiagramModelTreeNode)o).spokenText());
+				   *		builder.append(", ");
+				   * 	}
+				   *  Narrator.getInstance().speak(builder.toString(), null);
+				   */  
+				   TreePath path = getSelectionPath();
+				   DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent();
+				   NarratorFactory.getInstance().speak(treeNode.detailedSpokenText());
+				   InteractionLog.log(INTERACTIONLOG_SOURCE,"detailed info requested","");
+			   }
+		   });
+		   
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT,InputEvent.SHIFT_DOWN_MASK),"shift");
+		   getActionMap().put("shift",new AbstractAction(){
+			   @Override
+			   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)){
+							   InteractionLog.log(INTERACTIONLOG_SOURCE,"Could not get lock on node fro edge creation selection",DiagramElement.toLogString(node));
+							   SpeechOptionPane.showMessageDialog(
+									   SpeechOptionPane.getFrameForComponent(DiagramTree.this), 
+									   resources.getString("dialog.lock_failure.must_exist"), 
+									   SpeechOptionPane.INFORMATION_MESSAGE);
+							   SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK, new PlayerListener(){
+								   @Override
+								   public void playEnded() {
+									   NarratorFactory.getInstance().speak(currentPathSpeech());
+								   }
+							   });
+							   return;
+						   }
+						   selectNode(node);
+					   }
+				   }
+			   }
+		   });
+		   
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown");
+		   getActionMap().put("ctrldown",SpeechUtilities.getShutUpAction());
+		   
+		   /* make the tree ignore the page up and page down keys */
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0),"none");
+		   getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0),"none");
+	   }
+	
+	private static InputStream getTreeNodeSound(DiagramModelTreeNode node){
+		InputStream sound = null;
+		TreeNode[] newPath = node.getPath();
+		if(!node.isRoot()){
+			if(node.getChildCount() > 0){ // check whether it's the folder containing Node/Edge references
+				if(node.getChildAt(0) instanceof EdgeReferenceMutableTreeNode){
+					sound = ((EdgeReferenceMutableTreeNode)node.getChildAt(0)).getEdge().getSound();
+				}else{
+					sound = ((TypeMutableTreeNode)newPath[1]).getPrototype().getSound();
+				}
+			}else{
+				if(node instanceof NodeReferenceMutableTreeNode){
+					sound = ((NodeReferenceMutableTreeNode)node).getNode().getSound();
+				}else if(node instanceof EdgeReferenceMutableTreeNode){
+					sound = ((EdgeReferenceMutableTreeNode)node).getNode().getSound();
+				}else{
+					sound = ((TypeMutableTreeNode)newPath[1]).getPrototype().getSound();
+				}
+			}
+		}
+		return sound;
+	}
+	
+	@Override
+	public void setSelectionPath(TreePath path){
+		super.setSelectionPath(path);
+		scrollPathToVisible(path);
+	}
+	
+	private void notifyBorderReached(DiagramModelTreeNode n) {
+		SoundFactory.getInstance().play(SoundEvent.ERROR);
+	}
+	
+	@Override
+	public String convertValueToText(Object value,
+            boolean selected,
+            boolean expanded,
+            boolean leaf,
+            int row,
+            boolean hasFocus){
+		StringBuilder builder = new StringBuilder(super.convertValueToText(value, selected, expanded, leaf, row, hasFocus));
+		if(selectedNodes != null)
+			if(selectedNodes.contains(value)){
+				builder.insert(0, SELECTED_NODE_MARK_BEGIN);
+				builder.append(SELECTED_NODE_MARK_END);
+			}
+		return builder.toString();
+	}
+	
+	@Override
+	protected TreeModelListener createTreeModelListener(){
+		return new DiagramTreeModelHandler();
+	}
+	
+
+	private List<Node> selectedNodes;
+	private Diagram diagram;
+	private ResourceBundle resources;
+	private boolean treeSelectionListenerGateOpen;
+	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}
+	
+	/* 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
+	 * by another peer on a network, and therefore not only as a response to a user's action.  
+	 * The algorithm works as follows (being A the tree node selected before any handler method M being called):
+	 * 
+	 * if A ain't deleted as a result of M : do nothing
+	 * if A's deleted as a result of M's execution : say A was the n-th sibling select the new n-th sibling 
+	 *   or, if now the sibling nodes are less than n, select the one with highest index 
+	 * if no sibling nodes are still connected to the tree select the parent or the closest ancestor connected to the tree   
+	 */
+	private class DiagramTreeModelHandler extends JTree.TreeModelHandler{
+
+		@Override
+		public void treeStructureChanged(final TreeModelEvent e) {
+			/* check first if what we're removing is in the selection path */
+			TreePath path = e.getTreePath();
+			boolean isInSelectionPath = false;
+			for(Object t : getSelectionPath().getPath()){
+				if(path.getLastPathComponent() == t){
+					isInSelectionPath = true;
+					break;
+				}
+			}
+
+			if(isInSelectionPath){
+				Object[] pathArray = getSelectionPath().getPath();
+				DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
+				/* 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];
+					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];
+						if(parent.isNodeRelated(root) && parent.getChildCount() > 0){
+							super.treeStructureChanged(e);
+							setSelectionPath(new TreePath(((DefaultMutableTreeNode)parent.getLastChild()).getPath()));
+							break;
+						}
+					}
+				}
+			}else
+				super.treeStructureChanged(e);
+			repaint();
+		}
+
+		@Override
+		public void treeNodesChanged(final TreeModelEvent e){
+			TreePath path = getSelectionPath();
+			super.treeNodesChanged(e);
+			setSelectionPath(path);
+		}
+
+		@Override
+		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];
+			boolean isInSelectionPath = false;
+			for(Object t : getSelectionPath().getPath()){
+				if(removedTreeNode == t){
+					isInSelectionPath = true;
+					break;
+				}
+			}
+			DiagramModelTreeNode parentTreeNode = (DiagramModelTreeNode)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){
+				if(e.getSource() instanceof TreeModel){
+					/* update the path only if the node has been removed from the tree or         *
+					 * if the currently selected tree node is going to be removed by this action  *
+					 * Need to call collapsePath only if the source of the deletion is the tree   *
+					 * as otherwise the selected node is always a leaf  						  */
+					collapsePath(path);
+					setSelectionPath(path);
+				}else{ 
+					/* if we deleted from another source, then select the first non null node in the path *
+					 * including the deleted node. E.g. if we're deleting the first child of a parent     *
+					 * 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(
+								/* 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()));
+					}else{
+						/* 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];
+							if(itr.getPath()[0] == getModel().getRoot()){
+								TreePath newPath = new TreePath(itr.getPath()); 
+								setSelectionPath(newPath);
+								collapsePath(newPath);
+								break;
+							}
+						}
+					}
+				}
+			}else
+				super.treeNodesRemoved(e);
+
+			/* if the node was selected for edge creation, then remove it from the list */
+			DiagramModelTreeNode removedNode = (DiagramModelTreeNode)e.getChildren()[0];
+			selectedNodes.remove(removedNode);
+		}		
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Direction.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,138 @@
+/*  
+ 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/)
+
+ 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.geom.Point2D;
+
+/**
+   This class describes a direction in the 2D plane. 
+   A direction is a vector of length 1 with an angle between 0 
+   (inclusive) and 360 degrees (exclusive). There is also
+   a degenerate direction of length 0. 
+*/
+public class Direction
+{
+   /**
+      Constructs a direction (normalized to length 1).
+      @param dx the x-value of the direction
+      @param dy the corresponding y-value of the direction
+   */
+   public Direction(double dx, double dy)
+   {
+      x = dx;
+      y = dy;
+      double length = Math.sqrt(x * x + y * y);
+      if (length == 0) return;
+      x = x / length;
+      y = y / length;
+   }
+
+   /**
+      Constructs a direction between two points
+      @param p the starting point
+      @param q the ending point
+   */
+   public Direction(Point2D p, Point2D q)
+   {
+      this(q.getX() - p.getX(),
+         q.getY() - p.getY());
+   }
+   
+   public boolean isParallel(Direction d){
+	   if(equals(d.x,d.y,DELTA)||turn(180).equals(d.x,d.y,DELTA))
+		   return true;
+	   else 
+		   return false;
+   }
+
+   /**
+      Turns this direction by an angle.
+      @param angle the angle in degrees
+   */
+   public Direction turn(double angle){
+      double a = Math.toRadians(angle);
+      return new Direction(
+         x * Math.cos(a) - y * Math.sin(a),
+         x * Math.sin(a) + y * Math.cos(a));
+   }
+
+   /**
+      Gets the x-component of this direction
+      @return the x-component (between -1 and 1)
+   */
+   public double getX()
+   {
+      return x;
+   }
+
+   /**
+      Gets the y-component of this direction
+      @return the y-component (between -1 and 1)
+   */
+   public double getY()
+   {
+      return y;
+   }
+   
+   private boolean equals(double dx, double dy ){
+	   return ((x==dx)&&(y==dy));
+   }
+   
+   private boolean equals(double dx, double dy , double delta){
+	   return ((Math.abs(x-dx)<delta)&&(Math.abs(y-dy)<delta));
+   }
+   
+   @Override
+   public String toString(){
+	   return "("+x+","+y+")";
+   }
+
+   private double x;
+   private double y;
+   
+   private static final double DELTA = 0.05;
+
+   public static final Direction NORTH = new Direction(0, -1);
+   public static final Direction SOUTH = new Direction(0, 1);
+   public static final Direction EAST = new Direction(1, 0);
+   public static final Direction WEST = new Direction(-1, 0);
+   public static final Direction NONE = new Direction(0, 0);
+   
+   public static Direction compute(Point2D p, Point2D q){
+	  double x,y;
+	  x = p.getX() - q.getX();
+	  y = p.getY() - q.getY();
+      double length = Math.sqrt(x * x + y * y);
+      if (length == 0) 
+    	  return NONE;
+      x = x / length;
+      y = y / length;
+      if(NORTH.equals(x, y))
+    	  return NORTH;
+      if(SOUTH.equals(x, y))
+    	  return SOUTH;
+      if(EAST.equals(x, y))
+    	  return EAST;
+      if(WEST.equals(x, y))
+    	  return WEST;
+      return NONE;
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,679 @@
+/*  
+ 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/)
+
+ 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.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+
+/**
+ * An edge in a graph. Edge objects are used in a GraphPanel to render a diagram edge visually. 
+ * Edge objects are used in the tree representation of the diagram as well, as they're
+ * subclasses of {@link DiagramEdge}    
+ * 
+ */
+@SuppressWarnings("serial")
+public abstract class Edge extends DiagramEdge implements GraphElement{
+
+	public Edge(String type, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes,LineStyle style){
+		super(type,availableEndDescriptions);
+		this.minAttachedNodes = minAttachedNodes;
+		this.maxAttachedNodes = maxAttachedNodes;
+		this.style = style;
+		nodes = new ArrayList<Node>();
+	}
+   
+	@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);
+	   }
+   }
+
+   /**
+    * 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);
+				closestNode = nodes.get(0);
+			}
+			if(line.getP2().distance(aPoint) < minDist){
+				minDist = line.getP2().distance(aPoint);
+				closestNode = nodes.get(1);
+			}
+			return closestNode;
+		}else{
+			for(InnerPoint p : points){
+				for(GraphElement ge : p.getNeighbours())
+					if(ge instanceof Node){
+						Node n = (Node)ge;
+						Direction d = new Direction(p.getBounds().getCenterX() - n.getBounds().getCenterX(),p.getBounds().getCenterY() - n.getBounds().getCenterY());
+						if(n.getConnectionPoint(d).distance(aPoint) < minDist){
+							minDist = n.getConnectionPoint(d).distance(aPoint);
+							closestNode = n; 
+						}
+					}
+			}
+			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);
+
+	   /* 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(){
+		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);
+			}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);
+			}
+		}
+	}
+	
+	@Override
+	public abstract Rectangle2D getBounds();
+	
+	public void encode(Document doc, Element parent, List<Node> nodes){
+		parent.setAttribute(PersistenceManager.TYPE,getType());
+		parent.setAttribute(PersistenceManager.NAME, getName());
+		parent.setAttribute(PersistenceManager.ID, String.valueOf(getId()));
+
+		int numNodes = getNodesNum(); 
+		if(numNodes > 0){
+			Element nodesTag = doc.createElement(PersistenceManager.NODES);
+			parent.appendChild(nodesTag);
+			for(int i=0; i<numNodes;i++){
+				Element nodeTag = doc.createElement(PersistenceManager.NODE);
+				nodeTag.setAttribute(PersistenceManager.ID, String.valueOf(getNodeAt(i).getId()));
+				nodeTag.setAttribute(PersistenceManager.LABEL, getEndLabel(getNodeAt(i)));
+				nodesTag.appendChild(nodeTag);
+			}
+		}
+		 
+		if(!points.isEmpty()){
+			Element pointsTag = doc.createElement(PersistenceManager.POINTS);
+			parent.appendChild(pointsTag);
+			for(InnerPoint point : points){
+				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();
+				for(GraphElement ge : point.getNeighbours()){
+					if(ge instanceof Node){
+						builder.append(((Node)ge).getId());
+					}else{
+						builder.append(-(points.indexOf(ge)+1));
+					}
+					builder.append(" ");
+				}
+				builder.deleteCharAt(builder.length()-1);
+				neighboursTag.setTextContent(builder.toString());
+			}
+		}
+	}
+	
+	
+    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 {
+			connect(attachedNodes);
+		} catch (ConnectNodesException e) {
+			throw new IOException(e);
+		}
+		
+		for(int i=0; i < labels.size(); i++){
+			setEndLabel(attachedNodes.get(i), labels.get(i));
+		}
+		
+		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);
+			String id = pointTag.getAttribute(PersistenceManager.ID);
+			/* id of property nodes must be a negative value */
+			try{
+				if(Integer.parseInt(id) >= 0)
+					throw new IOException();
+			}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);
+			double dx = 0,dy = 0;
+			try{
+				dx = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.X));
+				dy = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.Y));
+			}catch(NumberFormatException nfe){
+				throw new IOException();
+			}
+			point.translate(new Point2D.Double(), dx, dy);
+		}
+		
+		/* remove the master inner point eventually created by connect */
+		/* we're going to replace it with the one in the XML file      */
+		points.clear();
+		/* re do the cycle when all the points id have been Map-ped */
+		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
+					ge = pointsId.get(neighbourId);
+				if(ge == null)
+					throw new IOException();
+				point.neighbours.add(ge);
+			}
+			points.add(point);
+		}
+    }
+    
+    @Override
+    public Point2D getConnectionPoint(Direction d){return null;}
+    
+	public int getMinAttachedNodes(){
+		return minAttachedNodes;
+	}
+	
+	public int getMaxAttachedNodes(){
+		return maxAttachedNodes;
+	}
+	
+	public LineStyle getStyle(){
+		return style;
+	}
+    
+	protected Point2D downPoint;
+	private List<Node> nodes;
+	
+	/* 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 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");
+
+	
+	protected static class InnerPoint implements GraphElement{
+	
+		public InnerPoint(){
+			bounds = new Rectangle2D.Double(0,0,DIM,DIM);
+			neighbours = new LinkedList<GraphElement>();
+		}
+	
+		@Override
+		public void startMove(Point2D p){}
+		
+		@Override
+		public void stopMove(){}
+	
+		@Override
+		public void draw(Graphics2D g2){
+			Color oldColor = g2.getColor();
+			g2.setColor(POINT_COLOR);
+			g2.fill(bounds);
+			g2.setColor(oldColor);
+		}
+
+		@Override
+		public boolean contains(Point2D p){
+			return bounds.contains(p);
+		}
+
+		@Override
+		public Point2D getConnectionPoint(Direction d){
+			return new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
+		}
+		
+		@Override
+		public void translate(Point2D p, double dx, double dy){
+			bounds.setFrame(bounds.getX() + dx,
+				bounds.getY() + dy, 
+				bounds.getWidth(), 
+				bounds.getHeight());
+		}
+		
+		@Override
+		public Rectangle2D getBounds(){
+			return (Rectangle2D)bounds.clone();
+		}
+		
+		public List<GraphElement> getNeighbours(){
+			return neighbours;
+		}
+		
+		public boolean hasNeighbour(GraphElement neighbour){
+			return neighbours.contains(neighbour);
+		}
+		
+		@Override
+		public String toString(){
+			return "EdgePoint: "+bounds.getCenterX()+"-"+bounds.getCenterY();
+		}
+		
+		private Rectangle2D bounds;
+		private List<GraphElement> neighbours; 
+		private static final int DIM = 5;
+	}
+	
+	public static class PointRepresentation {
+		public PointRepresentation(int size){
+			xs = new double[size];
+			ys = new double[size];
+			adjMatrix = new BitSet[size];
+			for(int i=0; i<size; i++){
+				adjMatrix[i] = new BitSet(size); 
+			}
+		}
+		public double xs[];
+		public double ys[];
+		public BitSet adjMatrix[];
+		public int nodeStart; // the index of the beginning of the nodes in the adjMatrix
+	}
+	
+	public PointRepresentation getPointRepresentation(){
+		PointRepresentation pr = new PointRepresentation(points.size()+nodes.size());
+		if(points.isEmpty()){ // two ended edge 
+			pr.xs[0] = nodes.get(0).getBounds().getCenterX();
+			pr.ys[0] = nodes.get(0).getBounds().getCenterY();
+			pr.xs[1] = nodes.get(1).getBounds().getCenterX();
+			pr.ys[1] = nodes.get(1).getBounds().getCenterY();
+			// we only need one edge, else it would be painted twice 
+			pr.adjMatrix[0].set(1);
+			pr.nodeStart = 0;
+		}else{
+			//[ point 1, point 2, point 3, ... , point n, node, 1 node 2, ... ,  node n ]
+			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));
+				  }
+			}
+			/* set the coordinates of the nodes, no adj matrix needed as the inner points are enough */
+			for(int i=0 ; i<nodes.size(); i++){
+				pr.xs[pSize+i] = nodes.get(i).getBounds().getCenterX();
+				pr.ys[pSize+i] = nodes.get(i).getBounds().getCenterY();
+			}
+		}
+		return pr;
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EdgePopupMenu.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,172 @@
+/*  
+ 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);
+  	    	   	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()); 
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EdgePopupMenu.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,13 @@
+
+
+menu.set_label=Set Label
+menu.choose_arrow_head=Choose 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,1951 @@
+/*  
+ 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/)
+
+ 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.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.KeyboardFocusManager;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.imageio.ImageIO;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.TreePath;
+
+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.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.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.ClientConnectionManager;
+import uk.ac.qmul.eecs.ccmi.network.Command;
+import uk.ac.qmul.eecs.ccmi.network.DiagramDownloader;
+import uk.ac.qmul.eecs.ccmi.network.DiagramShareException;
+import uk.ac.qmul.eecs.ccmi.network.NetDiagram;
+import uk.ac.qmul.eecs.ccmi.network.ProtocolFactory;
+import uk.ac.qmul.eecs.ccmi.network.Server;
+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.Narrator;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.utils.ExceptionHandler;
+import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+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.
+*/
+@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);
+
+      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();
+
+	   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().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();
+		   }
+	   });
+
+	   addWindowFocusListener(new WindowFocusListener(){
+		   @Override
+		   public void windowGainedFocus(WindowEvent evt) {
+			   if(evt.getOppositeWindow() == null) 
+				   NarratorFactory.getInstance().speak(resources.getString("window.focus"));
+		   }
+
+		   @Override
+		   public void windowLostFocus(WindowEvent evt) {
+			   if(evt.getOppositeWindow() == null)
+				   NarratorFactory.getInstance().speak(resources.getString("window.unfocus"));
+		   }
+	   });
+
+	   /* 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);
+
+	   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"));
+				}
+			   });
+		   }
+	   };
+   }
+   
+   private void initMenu(){
+	   ResourceFactory factory = new ResourceFactory(resources);
+
+	   JMenuBar menuBar = factory.createMenuBar();
+	   setJMenuBar(menuBar);
+	   
+	   /* --- FILE MENU --- */
+	   JMenu fileMenu = factory.createMenu("file");
+	   menuBar.add(fileMenu);
+
+	   /* menu items and listener added by addDiagramType function */
+	   newMenu = factory.createMenu("file.new");
+	   fileMenu.add(newMenu);
+
+	   JMenuItem fileOpenItem = factory.createMenuItem(
+			   "file.open", this, "openFile"); 
+	   fileMenu.add(fileOpenItem);      
+
+	   recentFilesMenu = factory.createMenu("file.recent");
+	   buildRecentFilesMenu();
+	   fileMenu.add(recentFilesMenu);
+
+	   fileSaveItem = factory.createMenuItem("file.save", this, "saveFile");
+	   fileMenu.add(fileSaveItem);
+	   
+	   fileSaveAsItem = factory.createMenuItem("file.save_as", this, "saveFileAs");
+	   fileMenu.add(fileSaveAsItem);
+
+	   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);
+
+	   JMenu editMenu = factory.createMenu("edit");
+	   menuBar.add(editMenu);
+
+	   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);
+
+	   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);
+	   
+	   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);
+					   }
+				   })
+		   );
+		   
+		   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);
+					   }
+				   }
+				   ));
+	   }
+	   
+	   /* --- 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);
+
+	   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){
+      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);
+					} catch (IOException exception) {
+						SpeechOptionPane.showMessageDialog(
+				        		 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 */ }
+      }        
+   }
+   
+   /**
+      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;
+
+	      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();}
+      }
+   }
+
+   /**
+      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++;
+		   }
+	   }
+
+	   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();
+
+	   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() {
+		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);
+
+				   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().setPlayerListener(new PlayerListener(){
+				   @Override
+				   public void playEnded() {
+					   NarratorFactory.getInstance().speak(message);
+				   }
+			   }, SoundEvent.ERROR);
+			   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]);
+
+		   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;
+
+			   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);
+	   }
+   }
+
+   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")
+		   };
+
+		   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()];
+			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]
+					   );
+			
+			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()));
+					   }
+				   });
+			}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){
+							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();
+			   }
+		   }
+	   }
+   }
+   
+   /**
+      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();}
+	   }
+   }
+
+   /**
+      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);
+   }
+   
+   /**
+      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);
+	   
+  }
+   
+   /**
+   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());
+	         }
+	      }));
+	}
+
+   /**
+    * 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
+ 	      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();
+
+	   /* 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){
+			try {
+				Diagram d = PersistenceManager.decodeDiagramTemplate(file);
+				addDiagramType(d);
+			} catch (IOException e) {
+				someFilesNotRead = true;
+				e.printStackTrace();
+			}
+		}
+	   return someFilesNotRead;
+   }
+   
+   private void iLog(String action,String args){
+	   InteractionLog.log("TREE",action,args);
+   }
+   
+   private void iLog(String message){
+	   InteractionLog.log(message);
+   }
+
+   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 ExtensionFilter extensionFilter;
+   private ExtensionFilter exportFilter;
+
+   private static final int DEFAULT_MAX_RECENT_FILES = 5;
+   private static final double GROW_SCALE_FACTOR = Math.sqrt(2);
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,259 @@
+### APPLICATION ###
+app.name=CCmI Editor
+app.copyright=Copyright (C) 2002...2005 Cay S. Horstmann (http://horstmann.com)
+app.version=0.1a
+files.name=CCmI Diagram files
+files.extension=.ccmi
+template.extension=.xml
+dir.home=ccmi_editor
+dir.templates=templates
+
+### EDITOR ### 
+
+error.version=You need Java version {0}
+files.image.name=Image Files
+files.image.extension=.jpg
+grabber.text=Select
+
+window.focus=Editor window focused
+window.unfocus=Editor window unfocused
+window.tab=Tab, {0}
+
+#### DIALOGS ###
+dialog.about={0} version {1}\u000A\u000A{2}\u000A\u000A{3}
+dialog.about.description=Collaborative Cross-modal Interfaces\u000A \
+The Collaborative Cross-modal Interfaces (CCmI) project is a Research\u000A \
+Councils UK Digital Economy Programme funded project that aims to explore\u000A \
+the use of multi-modal input and output technologies (audio, haptics, graphics)\u000A \
+to improve the accessibility of collaboration using diagrams in the workplace.\u000A \
+The challenge is to design support for collaboration where participants have\u000A \
+differing access to modalities - we refer to these situations\u000A as cross-modal collaboration.
+dialog.about.license=This program comes with ABSOLUTELY NO WARRANTY.\u000AThis is free software, and you are welcome to redistribute it\u000Aunder certain conditions.\
+Select License in the Help menu for details.
+dialog.about.title=About
+dialog.license.title=License
+
+dialog.ok_button=Ok
+dialog.cancel_button=Cancel
+
+dialog.overwrite=Another file with the same name already exists. Do you want to overwrite it ?
+dialog.properties=Properties
+dialog.error.title=Error
+dialog.error.filesnotread=Error: One or more templates files could not be read properly
+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.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.template_created=Diagram {0} created
+dialog.file_saved=File Saved
+dialog.downloading_diagram=Downloading diagram: {0}
+
+dialog.warning.title=Warning
+dialog.warning.null_modifiers=Nothing to edit for {0}
+
+dialog.input.bookmark.select.add_remove=What would you like to do ?
+dialog.input.bookmark.select.add=Add Bookmark
+dialog.input.bookmark.select.remove=Remove Bookmark
+dialog.input.bookmark.text=New bookmark. Enter name
+dialog.input.bookmark.text.already_existing=The chosen bookmark already exists
+dialog.input.bookmark.text.empty=Bookmarks cannot be empty
+dialog.input.bookmark.select.bookmark=Select bookmark
+dialog.input.bookmark.select.notfound=No bookmarks available
+dialog.input.bookmark.delete=Select bookmark to remove
+dialog.input.bookmark.title=Bookmark
+dialog.input.notes.title=Notes
+dialog.input.notes.text=Edit notes for 
+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.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
+dialog.input.selected_type.select=Select which type to jump to
+dialog.input.check_modifiers=Editing {0}.  
+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.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 ?
+dialog.confirm.exit={0} Unsaved Diagrams\u000ADo you want to save changes?
+dialog.confirm.close=Unsaved diagram.\u000ADo you want to save changes?
+dialog.confirm.title=Confirm
+
+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.lock_failure.name=Object name is being edited by another user
+dialog.lock_failure.properties=Node properties are being edited by another user
+dialog.lock_failure.end_label=Edge is being edited by another user 
+dialog.lock_failure.arrow_head=Edge arrow heads are being edited by another user 
+dialog.lock_failure.move=Object is being moved by another user
+dialog.lock_failure.notes=Tree node is being edited by another user
+dialog.lock_failure.bookmark=Tree node is being edited by another user
+dialog.lock_failure.must_exist=Element is candidate for deletion by another user
+
+dialog.property_editor.title=Property Editor
+dialog.property_editor.error.property_null=Properties cannot be null
+dialog.property_editor.edit_modifiers_button=Edit Modifiers
+
+dialog.modifier_editor.title=Modifier Editor
+
+dialog.speech_option_pane.download=Download
+dialog.speech_option_pane.input=Input
+dialog.speech_option_pane.select=Select
+dialog.speech_option_pane.confirm=Confirm
+dialog.speech_option_pane.modifiers=Select Modifiers
+dialog.speech_option_pane.message= {0}. Press OK to confirm
+dialog.speech_option_pane.cancel=Cancel
+
+dialog.file_chooser.file_type=File Type:
+dialog.file_chooser.file_name=file Name:
+
+dialog.speech_rate.message=Speech rate set to {0} 
+
+server.shutdown_msg=request by user
+server.not_running_exc=Server not running
+#### OPTIONS ####
+options.jump.reference=Reference
+options.jump.type=Type
+options.jump.diagram=Diagram
+options.jump.bookmark=Bookmark
+####    SPEECH    ####
+speech.cancelled=Cancelled, {0}
+speech.delete.element.ack={0} deleted, {1}
+speech.deleted.property.ack={0} deleted, {1} 
+speech.delete.bookmark.ack= bookmark {0} deleted, {1}
+speech.no_bookmarks=No bookmarks available
+speech.note.updated=Note updated 
+speech.empty_property=Empty string, nothing created. {0}
+speech.empty_label=Empty label
+speech.selected=selected
+speech.unselected=unselected
+speech.input.property.ack={0} created, 
+speech.input.node.ack={0} , created, 
+speech.input.edge.ack=, connected, 
+speech.input.edge.ack2= and 
+speech.invalid_ip=invalid IP address
+
+speech.diagram_closed= {0} closed.
+speech.node_selected={0} selected
+speech.node_unselected={0} unselected
+speech.jump=Jump to {0}
+
+### TABBED PANE ### 
+tab.new_tab=new {0}
+tab.new_tab_id=new {0} ({1})
+
+### MENU ### 
+file.text=File
+file.mnemonic=F
+file.new.text=New Diagram
+file.new.mnemonic=N
+file.open.text=Open...   
+file.open.mnemonic=O
+file.open.accelerator=ctrl O
+file.recent.text=Recent files
+file.recent.mnemonic=R
+file.save.text=Save
+file.save.mnemonic=S
+file.save.accelerator=ctrl S
+file.save_as.text=Save as...
+#file.save_as.mnemonic=A
+file.close.text=Close
+file.export_image.text=Export image
+file.export_image.mnemonic=E
+file.print.text=Print
+file.print.mnemonic=P
+file.exit.text=Exit
+file.exit.mnemonic=X
+file.exit.accelerator=ctrl X
+edit.text=Edit
+edit.mnemonic=E
+edit.jump.text=Jump to
+edit.jump.mnemonic=J
+edit.jump.accelerator=ctrl J
+edit.properties.text=Properties
+edit.properties.mnemonic=P
+edit.delete.text=Delete
+edit.delete.mnemonic=D
+edit.delete.accelerator=ctrl DELETE
+edit.insert.text=Insert/Edit
+edit.insert.mnemonic=i
+edit.insert.accelerator=ctrl ENTER
+edit.rename.text=Rename
+edit.rename.mnemonic=R
+edit.rename.accelerator=ctrl R
+edit.bookmark.text=Add/Remove Bookmark
+edit.bookmark.mnemonic=B
+edit.bookmark.accelerator=ctrl B
+edit.edit_note.text=Edit Note
+edit.edit_note.mnemonic=N
+edit.edit_note.accelerator=ctrl N
+edit.select_next.text=Select Next
+edit.select_next.mnemonic=N
+edit.select_next.accelerator=ctrl RIGHT
+edit.select_previous.text=Select Previous
+edit.select_previous.mnemonic=P
+edit.select_previous.accelerator=ctrl LEFT
+edit.edit.text=Edit 
+edit.edit.mnemonics=U
+edit.edit.accelerator=ctrl U
+edit.locate.text=Find
+edit.locate.mnemonics=F
+edit.locate.accelerator=ctrl F 
+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
+view.zoom_out.mnemonic=O
+view.zoom_out.accelerator=ctrl MINUS
+view.zoom_in.text=Zoom in
+view.zoom_in.mnemonic=I
+view.zoom_in.accelerator=ctrl EQUALS
+view.smaller_grid.text=Smaller grid
+view.smaller_grid.mnemonic=S
+view.grow_drawing_area.text=Grow drawing area
+view.grow_drawing_area.mnemonic=G
+view.clip_drawing_area.text=Clip drawing area
+view.clip_drawing_area.mnemonic=C
+view.larger_grid.text=Larger grid
+view.larger_grid.mnemonic=L
+view.hide_grid.text=Hide grid
+view.hide_grid.mnemonic=H
+view.change_laf.text=Change Look&Feel
+view.change_laf.mnemonic=K
+template.text=Template
+template.mnemonic=T
+collab.text=Collaboration
+collab.mnemonic=C
+collab.start_server.text=Start Server
+collab.stop_server.text=Stop Server
+collab.share_diagram.text=Share Diagram
+collab.open_shared_diagram.text= Open Shared Diagram
+help.text=Help
+help.mnemonic=H
+help.about.text=About
+#help.about.mnemonic=A
+help.license.text=License
+help.license.mnemonic=L
+
+
+no_arrow_string=None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorTabbedPane.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,136 @@
+/*  
+ 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.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;
+
+/**
+ * 
+ * The tabbed pane of the editor. On each tab a {@link DiagramPanel} is displayed. 
+ *
+ */
+@SuppressWarnings("serial")
+public class EditorTabbedPane extends JTabbedPane {
+	public EditorTabbedPane(JFrame frame){
+		this.frame = 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());
+			}
+		});
+		
+		/* shut up the narrator upon pressing ctrl */
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown");
+		getActionMap().put("ctrldown",new AbstractAction(){
+			public void actionPerformed(ActionEvent evt){
+				NarratorFactory.getInstance().shutUp();
+			}
+		});
+	}
+
+	public void setComponentTabTitle(Component component, String title){
+		int index = indexOfComponent(component); 
+		if(index == -1)
+			return;
+		setTitleAt(index,title);
+	}
+	
+	public String getComponentTabTitle(Component component){
+		int index = indexOfComponent(component);
+		if(index == -1)
+			return null;
+		return getTitleAt(index);
+	}
+	
+	public void refreshComponentTabTitle(Component component){
+		setComponentTabTitle(component,component.getName());
+	}
+	
+	@Override
+	public DiagramPanel getComponentAt(int n){
+		return (DiagramPanel)super.getComponent(n);
+	}
+	
+	public int getDiagramNameTabIndex(String diagramName){
+		for(int i=0; i<getTabCount();i++){
+			DiagramPanel dPanel = getComponentAt(i); 
+			if(diagramName.equals(dPanel.getDiagram().getName())){
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	public int getPathTabIndex(String path){
+		for(int i=0; i<getTabCount();i++){
+			DiagramPanel dPanel = getComponentAt(i); 
+			if(path.equals(dPanel.getFilePath())){
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	private JFrame frame;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/ExtensionFilter.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,86 @@
+/*  
+ 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/)
+
+ 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.io.File;
+import java.util.StringTokenizer;
+import javax.swing.filechooser.FileFilter;
+
+/**
+   A file filter that accepts all files with a given set
+   of extensions.
+*/
+public class ExtensionFilter 
+   extends FileFilter
+{
+   /**
+      Constructs an extension file filter.
+      @param description the description (e.g. "Woozle files")
+      @param extensions the accepted extensions (e.g.
+      new String[] { ".woozle", ".wzl" })
+   */
+   public ExtensionFilter(String description, 
+      String[] extensions){
+      this.description = description; 
+      this.extensions = extensions;
+   }
+
+   /**
+      Constructs an extension file filter.
+      @param description the description (e.g. "Woozle files")
+   */
+   public ExtensionFilter(String description, 
+      String extensions){
+      this.description = description; 
+      StringTokenizer tokenizer = new StringTokenizer(
+         extensions, "|");
+      this.extensions = new String[tokenizer.countTokens()];
+      for (int i = 0; i < this.extensions.length; i++)
+         this.extensions[i] = tokenizer.nextToken();
+   }
+   
+   @Override
+   public boolean accept(File f){  
+      if (f.isDirectory()) return true;
+      String fname = f.getName().toLowerCase();
+      for (int i = 0; i < extensions.length; i++)
+         if (fname.endsWith(extensions[i].toLowerCase())) 
+            return true;
+      return false;
+   }
+   
+   @Override
+   public String getDescription(){ 
+      return description;
+   }
+   
+   public String[] getExtensions(){
+      return extensions;
+   }
+   
+   @Override
+   public String toString(){
+	   return description;
+   }
+
+   private String description;
+   private String[] extensions;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,254 @@
+package uk.ac.qmul.eecs.ccmi.gui;
+
+import java.awt.Frame;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ResourceBundle;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+
+import uk.ac.qmul.eecs.ccmi.gui.filechooser.FileChooser;
+import uk.ac.qmul.eecs.ccmi.gui.filechooser.FileChooserFactory;
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+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;
+      
+   }
+
+   /**
+    * 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();
+   }
+
+   /**
+    * 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);
+         }
+      }
+  
+
+      /* 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);
+	   }
+   }
+
+   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
+   @param toBeRemoved the extension that is to be
+   removed before adding the desired extension. Use
+   null if nothing needs to be removed. 
+   @param desired the desired extension (e.g. ".png"),
+   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;
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Finder.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,114 @@
+/*  
+ 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.geom.Point2D;
+import java.util.Collection;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
+
+/**
+ * 
+ * A utility class which provides methods for searching either a node or an edge 
+ * in a collection or array.
+ *
+ */
+public abstract class Finder {
+	public static Node findNode(String nodeClass,Node[] prototypes){
+		for(Node n : prototypes){
+			if(n.getType().equals(nodeClass)){
+				return n;
+			}
+		}
+		return null;
+	}
+	
+	public static Edge findEdge(String edgeClass,Edge[] prototypes){
+		for(Edge e : prototypes){
+			if(e.getType().equals(edgeClass)){
+				return e;
+			}
+		}
+		return null;
+	}
+	
+	public static Node findNode(Long id, Collection<Node> collection){
+		for(Node n : collection)
+			if(n.getId() == id)
+				return n;
+		return null;
+	}
+	
+	public static Node findNode(Point2D p, Collection<Node> collection){
+		for (Node n : collection)
+			if (n.contains(p)) 
+				return n;
+		return null;
+	}
+	
+	public static Edge findEdge(Point2D p, Collection<Edge> collection){
+		for (Edge e : collection)
+			if (e.contains(p)) 
+				return e;
+		return null;
+	}
+	
+	public static Edge findEdge(Long id, Collection<Edge> collection){
+		for(Edge e : collection)
+			if(e.getId() == id)
+				return e;
+		return null;
+	}
+	
+	public static DiagramElement findElement(Long id, Collection<DiagramElement> collection){
+		for(DiagramElement e : collection)
+			if(e.getId() == id)
+				return e;
+		return null;
+	}
+	
+	public static DiagramElement findElementByHashcode(long identityHashcode, Collection<DiagramElement> collection){
+		for(DiagramElement de : collection){
+			if(System.identityHashCode(de) == identityHashcode){
+				return de;
+			}
+		}
+		return null;
+	}
+	
+	/**
+	 * Return the tree node whose path is described by the variable path
+	 * where path contains the indexes returned by each node n of the path upon calling n.getParent().getChildAt(n) 
+	 * 
+	 * @param path
+	 * @param root
+	 * @return
+	 */
+	public static DiagramModelTreeNode findTreeNode(int[] path, DiagramModelTreeNode root){
+		DiagramModelTreeNode retVal = root;
+		for(int i=0;i<path.length;i++){
+			if(retVal.getChildCount() <= path[i])
+				return null;
+			retVal = retVal.getChildAt(path[i]);
+		}
+		return retVal;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphElement.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,47 @@
+/*  
+ 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.Graphics2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+
+/**
+ * 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.  
+ *
+ */
+public interface GraphElement {
+	public void draw(Graphics2D g2);
+
+	public void translate(Point2D p, double dx, double dy);
+
+	public void stopMove();
+	
+	public void startMove(Point2D p);
+	
+	public Rectangle2D getBounds();
+	
+	public Point2D getConnectionPoint(Direction d);
+	
+	public boolean contains(Point2D p);
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,757 @@
+/*  
+ 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/)
+
+ 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.Color;
+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;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+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.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
+
+/**
+ * A panel to draw a graph
+ */
+@SuppressWarnings("serial")
+public class GraphPanel extends JPanel{
+	/**
+	 * Constructs a graph.
+	 * @param aDiagram a diagram to paint in the graph 
+	 * @param a aToolbar a toolbar containing the node and edges prototypes for creating 
+	 * elements in the graph. 
+	 */
+
+	public GraphPanel(Diagram aDiagram, GraphToolbar aToolbar) {
+		grid = new Grid();
+		gridSize = GRID;
+		grid.setGrid((int) gridSize, (int) gridSize);
+		zoom = 1;
+		toolbar = aToolbar;
+		setBackground(Color.WHITE);
+		wasMoving = false;
+		minBounds = null;
+
+		this.model = aDiagram.getCollectionModel();
+		synchronized(model.getMonitor()){
+			edges = new LinkedList<Edge>(model.getEdges());
+			nodes = new LinkedList<Node>(model.getNodes());
+		}
+		setModelUpdater(aDiagram.getModelUpdater());
+
+		selectedElements = new HashSet<DiagramElement>();
+		moveLockedElements = new HashSet<Object>();
+
+		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 
+		 * and paints again all the nodes and edges.
+		 */
+		model.addCollectionListener(new CollectionListener(){
+			@Override
+			public void elementInserted(final CollectionEvent e) {
+				DiagramElement element = e.getDiagramElement(); 
+				if(element instanceof Node)
+					nodes.add((Node)element);
+				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 
+					setElementSelected(e.getDiagramElement());
+					dragMode = DRAG_NODE;
+				}
+				revalidate();
+				repaint();
+			}
+			@Override
+			public void elementTakenOut(final CollectionEvent e) {
+				DiagramElement element = e.getDiagramElement(); 
+				if(element instanceof Node){
+					if(nodePopup != null && nodePopup.nodeRef.equals(element))
+						nodePopup.setVisible(false);
+					nodes.remove(element);
+				}
+				else{
+					if(edgePopup != null && edgePopup.edgeRef.equals(element))
+						edgePopup.setVisible(false);
+					edges.remove(element);
+				}
+				checkBounds(e.getDiagramElement(),true);
+				removeElementFromSelection(e.getDiagramElement());
+				revalidate();
+				repaint();
+			}
+			@Override
+			public void elementChanged(final ElementChangedEvent e) {
+				/* we changed the position of an element and might need to update the boundaries */
+				if(e.getChangeType().equals("stop_move")){
+					checkBounds(e.getDiagramElement(),false);
+				}
+				revalidate();
+				repaint();
+			}
+		});
+		/*  ---------------------------------------------------------------------------   */
+
+		/*  -------------   MOUSE LISTENERS --------------------------------------------        
+		 *  For pressed and released mouse click and moved mouse  
+		 */    
+		addMouseListener(new MouseAdapter(){
+			@Override
+			public void mousePressed(MouseEvent event){
+				requestFocusInWindow();
+				final Point2D mousePoint = new Point2D.Double(
+						(event.getX()+minX)/zoom,
+						(event.getY()+minY)/zoom 
+				);
+				boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0; 
+				Node n = Finder.findNode(mousePoint,nodes);
+				Edge e = Finder.findEdge(mousePoint,edges);
+
+				Object tool = toolbar.getSelectedTool();
+				/* - right click - */
+				if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) {
+					if(e != null){
+						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);
+								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);
+								edgePopup = pop;
+								pop.show(GraphPanel.this, event.getX(), event.getY());
+							}
+						}
+					}else if(n != null){
+						NodePopupMenu pop = new NodePopupMenu(n,GraphPanel.this,modelUpdater);
+						nodePopup = pop;
+						pop.show(GraphPanel.this, event.getX(), event.getY());
+					}else
+						return;
+				}
+
+				/* - one click && palette == select - */
+				else if (tool == null){
+					if(n != null){ // node selected
+						if (isCtrl)
+							addElementToSelection(n,false);
+						else
+							setElementSelected(n);
+						dragMode = DRAG_NODE;
+					}else if (e != null){ // edge selected
+						if (isCtrl){
+							addElementToSelection(e,false);
+							dragMode = DRAG_NODE;
+						}else{
+							setElementSelected(e);
+							modelUpdater.startMove(e, mousePoint);
+							dragMode = DRAG_EDGE;
+						}
+					}else{  // nothing selected : make selection lasso
+						if (!isCtrl)
+							clearSelection();
+						dragMode = DRAG_LASSO;
+					}
+				}
+				/* - one click && palette == node - */
+				else {
+					/* click on an already existing node = select it*/
+					if (n != null){
+						if (isCtrl)
+							addElementToSelection(n,false);
+						else 
+							setElementSelected(n);
+						dragMode = DRAG_NODE;
+					}else{
+						Node prototype = (Node) tool;
+						Node newNode = (Node) prototype.clone();
+						Rectangle2D bounds = newNode.getBounds();
+						/* perform the translation from the origin */
+						newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(), 
+								mousePoint.getY() - bounds.getY());
+						/* 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);
+					}	
+				}
+
+				lastMousePoint = mousePoint;
+				mouseDownPoint = mousePoint;
+				repaint();
+			}
+
+			@Override
+			public void mouseReleased(MouseEvent event){
+				final Point2D mousePoint = new Point2D.Double(
+						(event.getX()+minX)/zoom,
+						(event.getY()+minY)/zoom 
+				);
+				if(lastSelected != null){
+					if(lastSelected instanceof Node){
+						if(wasMoving){
+							iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY());
+							for(Object element : moveLockedElements){
+								modelUpdater.stopMove((GraphElement)element);
+								modelUpdater.yieldLock((DiagramModelTreeNode)element, Lock.MOVE);
+							}
+							moveLockedElements.clear();
+						}
+					}else{ // instanceof Edge 
+						if(wasMoving){
+							iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY());
+							if(moveLockedEdge != null){
+								modelUpdater.stopMove(moveLockedEdge);
+								modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE);
+								moveLockedEdge = null;
+							}
+						}
+					}
+				}
+				dragMode = DRAG_NONE;
+				wasMoving = false;
+				repaint();
+			}
+		});
+
+		addMouseMotionListener(new MouseMotionAdapter(){
+			public void mouseDragged(MouseEvent event){
+				Point2D mousePoint = new Point2D.Double(
+						(event.getX()+minX)/zoom,
+						(event.getY()+minY)/zoom 
+				);
+				boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0; 
+
+				if (dragMode == DRAG_NODE){               
+					/* translate selected nodes (edges as well) */
+					double dx = mousePoint.getX() - lastMousePoint.getX();
+					double dy = mousePoint.getY() - lastMousePoint.getY();
+					if(!wasMoving){
+						wasMoving = true;
+						/* when the motion starts, we need to get the move-lock from the server */
+						Iterator<DiagramElement> iterator = selectedElements.iterator();
+						while(iterator.hasNext()){
+							DiagramElement element = iterator.next(); 
+							if(modelUpdater.getLock(element, Lock.MOVE)){
+								moveLockedElements.add(element);
+							}else{
+								iLog("Could not get move lock for element",DiagramElement.toLogString(element));
+								iterator.remove();
+							}
+						}
+						iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY());
+					}
+
+					for (DiagramElement selected : selectedElements){
+						if(selected instanceof Node)
+							modelUpdater.translate((Node)selected, lastMousePoint, dx, dy);
+						else
+							modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy);
+					}
+				} else if(dragMode == DRAG_EDGE){
+					if(!wasMoving){
+						wasMoving = true;
+						if(modelUpdater.getLock(lastSelected, Lock.MOVE))
+							moveLockedEdge = (Edge)lastSelected;
+						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()));
+				} else if (dragMode == DRAG_LASSO){
+					double x1 = mouseDownPoint.getX();
+					double y1 = mouseDownPoint.getY();
+					double x2 = mousePoint.getX();
+					double y2 = mousePoint.getY();
+					Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), 
+							Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
+					for (Node n : GraphPanel.this.nodes){
+						Rectangle2D bounds = n.getBounds();
+						if(!isCtrl && !lasso.contains(bounds)){
+							removeElementFromSelection(n);
+						}
+						else if (lasso.contains(bounds)){
+							addElementToSelection(n,true);
+						}
+					}
+					if(selectedElements.size() != oldLazoSelectedNum){
+						StringBuilder builder = new StringBuilder();
+						for(DiagramElement de : selectedElements)
+							builder.append(DiagramElement.toLogString(de)).append(' ');
+						iLog("added by lazo",builder.toString());
+					}
+					oldLazoSelectedNum = selectedElements.size();
+				}
+				lastMousePoint = mousePoint;
+			}
+		});
+	}
+	/*  ---------------------------------------------------------------------------   */
+
+	@Override
+	public void paintComponent(Graphics g){
+		super.paintComponent(g);
+		paintGraph(g);
+	}
+
+	public void paintGraph(Graphics g){
+		Graphics2D g2 = (Graphics2D) g;
+		g2.translate(-minX, -minY);
+		g2.scale(zoom, zoom);
+		Rectangle2D bounds = getBounds();
+		Rectangle2D graphBounds = getGraphBounds();
+		if (!hideGrid) grid.draw(g2, new Rectangle2D.Double(minX, minY, 
+				Math.max(bounds.getMaxX() / zoom, graphBounds.getMaxX()), 
+				Math.max(bounds.getMaxY() / zoom, graphBounds.getMaxY())));
+
+		/* draw nodes and edges */  
+		for (Edge e : edges)
+			e.draw(g2);	
+		for (Node n : nodes)
+			n.draw(g2);
+
+		for(DiagramElement selected : selectedElements){
+			if (selected instanceof Node){
+				Rectangle2D grabberBounds = ((Node) selected).getBounds();
+				drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMinY());
+				drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMaxY());
+				drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMinY());
+				drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMaxY());
+			}
+			else if (selected instanceof Edge){
+				for(Point2D p : ((Edge)selected).getConnectionPoints())
+					drawGrabber(g2, p.getX(), p.getY()); 
+			}
+		}                 
+
+		if (dragMode == DRAG_LASSO){
+			Color oldColor = g2.getColor();
+			g2.setColor(GRABBER_COLOR);
+			double x1 = mouseDownPoint.getX();
+			double y1 = mouseDownPoint.getY();
+			double x2 = lastMousePoint.getX();
+			double y2 = lastMousePoint.getY();
+			Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), 
+					Math.min(y1, y2), Math.abs(x1 - x2) , Math.abs(y1 - y2));
+			g2.draw(lasso);
+			g2.setColor(oldColor);
+			repaint();
+		}    
+	}
+
+	/**
+	 * Draws a single "grabber", a filled square
+	 * @param g2 the graphics context
+	 * @param x the x coordinate of the center of the grabber
+	 * @param y the y coordinate of the center of the grabber
+	 */
+	static void drawGrabber(Graphics2D g2, double x, double y){
+		final int SIZE = 5;
+		Color oldColor = g2.getColor();
+		g2.setColor(GRABBER_COLOR);
+		g2.fill(new Rectangle2D.Double(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE));
+		g2.setColor(oldColor);
+	}
+
+	@Override
+	public Dimension getPreferredSize(){
+		Rectangle2D graphBounds = getGraphBounds();
+		return new Dimension((int) (zoom * graphBounds.getMaxX()),
+				(int) (zoom * graphBounds.getMaxY()));
+	}
+
+	/**
+	 * Changes the zoom of this panel. The zoom is 1 by default and is multiplied
+	 * by sqrt(2) for each positive stem or divided by sqrt(2) for each negative
+	 * step.
+	 * @param steps the number of steps by which to change the zoom. A positive
+	 * value zooms in, a negative value zooms out.
+	 */
+	public void changeZoom(int steps){
+		final double FACTOR = Math.sqrt(2);
+		for (int i = 1; i <= steps; i++)
+			zoom *= FACTOR;
+		for (int i = 1; i <= -steps; i++)
+			zoom /= FACTOR;
+		revalidate();
+		repaint();
+	}
+
+	/**
+	 * Changes the grid size of this panel. The zoom is 10 by default and is
+	 * multiplied by sqrt(2) for each positive stem or divided by sqrt(2) for
+	 * each negative step.
+	 * @param steps the number of steps by which to change the zoom. A positive
+	 * value zooms in, a negative value zooms out.
+	 */
+	public void changeGridSize(int steps){
+		final double FACTOR = Math.sqrt(2);
+		for (int i = 1; i <= steps; i++)
+			gridSize *= FACTOR;
+		for (int i = 1; i <= -steps; i++)
+			gridSize /= FACTOR;
+		grid.setGrid((int) gridSize, (int) gridSize);
+		repaint();
+	}
+
+	private void addElementToSelection(DiagramElement element, boolean byLasso){
+		/* if not added to selected elements by including it in the lasso, the element is moved    *
+		 * to the back of the collection so that it will be painted on the top on the next refresh */
+		if(!byLasso)
+			if(element instanceof Node){
+				/* put the node in the last position so that it will be drawn on the top */
+				nodes.remove(element);
+				nodes.add((Node)element);
+				iLog("addeded node to selected",DiagramElement.toLogString(element));
+			}else{
+				/* put the edge in the last position so that it will be drawn on the top */
+				edges.remove(element);
+				edges.add((Edge)element);
+				iLog("addeded edge to selected",DiagramElement.toLogString(element));
+			}
+		if(selectedElements.contains(element)){
+			lastSelected = element;
+			return;
+		}
+		lastSelected = element;
+		selectedElements.add(element);
+		return;
+	}
+
+	private void removeElementFromSelection(DiagramElement element){
+		if (element == lastSelected){
+			lastSelected = null;
+		}
+		if(selectedElements.contains(element)){
+			selectedElements.remove(element);
+		}
+	}
+
+	private void setElementSelected(DiagramElement element){ 
+		/* clear the selection */
+		selectedElements.clear();
+		lastSelected = element;
+		selectedElements.add(element);
+		if(element instanceof Node){
+			nodes.remove(element);
+			nodes.add((Node)element);
+			iLog("node selected",DiagramElement.toLogString(element));
+		}else{
+			edges.remove(element);
+			edges.add((Edge)element);
+			iLog("edge selected",DiagramElement.toLogString(element));
+		}
+	}
+
+	private void clearSelection(){
+		iLog("selection cleared","");
+		selectedElements.clear();
+		lastSelected = null;
+	}
+
+	/**
+	 * Sets the value of the hideGrid property
+	 * @param newValue true if the grid is being hidden
+	 */
+	public void setHideGrid(boolean newValue){
+		hideGrid = newValue;
+		repaint();
+	}
+
+	/**
+	 * Gets the value of the hideGrid property
+	 * @return true if the grid is being hidden
+	 */
+	public boolean getHideGrid(){
+		return hideGrid;
+	}
+
+	/**
+   Gets the smallest rectangle enclosing the graph
+   @return the bounding rectangle
+	 */
+	public Rectangle2D getMinBounds() { return minBounds; }
+	public void setMinBounds(Rectangle2D newValue) { minBounds = newValue; }
+
+	public Rectangle2D getGraphBounds(){
+		Rectangle2D r = minBounds;
+		for (Node n : nodes){
+			Rectangle2D b = n.getBounds();
+			if (r == null) r = b;
+			else r.add(b);
+		}
+		for (Edge e : edges){
+			r.add(e.getBounds());
+		}
+		return r == null ? new Rectangle2D.Double() : new Rectangle2D.Double(r.getX(), r.getY(), 
+				r.getWidth() + Node.SHADOW_GAP + Math.abs(minX), r.getHeight() + Node.SHADOW_GAP + Math.abs(minY));
+	}
+
+	public void setModelUpdater(DiagramModelUpdater modelUpdater){
+		this.modelUpdater = modelUpdater;
+	}
+
+	private void iLog(String action,String args){
+		InteractionLog.log("GRAPH",action,args);
+	}
+
+	private void checkBounds(DiagramElement de, boolean wasRemoved){
+		GraphElement ge; 
+		if(de instanceof Node)
+			ge = (Node)de;
+		else 
+			ge = (Edge)de;
+		if(wasRemoved){
+			if(ge == top){
+				top = null;
+				minY = 0;
+				Rectangle2D bounds;
+				for(Edge e : edges){
+					bounds = e.getBounds(); 
+					if(bounds.getY() < minY){
+						top = e;
+						minY = bounds.getY();
+					}
+				}
+				for(Node n : nodes){
+					bounds = n.getBounds();
+					if(bounds.getY() < minY){
+						top = n;
+						minY = bounds.getY();
+					}
+				}
+			}
+			if(ge == left){
+				minX = 0;
+				left = null;
+				synchronized(model.getMonitor()){
+					Rectangle2D bounds;
+					for(Edge e : model.getEdges()){
+						bounds = e.getBounds(); 
+						if(bounds.getX() < minX){
+							left = e;
+							minX = bounds.getX();
+						}
+					}
+					for(Node n : model.getNodes()){
+						bounds = n.getBounds(); 
+						if(bounds.getX() < minX){
+							left = n;
+							minX = bounds.getX();
+						}
+					}
+				}
+			}
+		}else{ // was added or translated
+			Rectangle2D bounds = ge.getBounds();
+		if(top == null){
+			if(bounds.getY() < 0){
+				top = ge;
+				minY = bounds.getY(); 
+			}
+		}else if(ge == top){ //the top-most has been translated recalculate the new top-most, as itf it were deleted
+			checkBounds(de, true);
+		}else if(bounds.getY() < top.getBounds().getY()){
+			top = ge;
+			minY = bounds.getY();
+		}
+
+		if(left == null){
+			if(bounds.getX() < 0){
+				left = ge;
+				minX = bounds.getX(); 
+			}
+		}else if(ge == left){
+			checkBounds(de,true);//the left-most has been translated recalculate the new left-most, as if it were deleted
+		}
+		else if(bounds.getX() < left.getBounds().getX()){
+			left = ge;
+			minX = bounds.getX();
+		}
+		}
+	}
+
+	private class innerEdgeListener implements GraphToolbar.EdgeCreatedListener {
+		@Override
+		public void edgeCreated(Edge e) {
+			ArrayList<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(selectedElements.size());
+			for(DiagramElement element : selectedElements){
+				if(element instanceof Node)
+					nodesToConnect.add((Node)element);
+			}
+			try {
+				e.connect(nodesToConnect);
+				modelUpdater.insertInCollection(e);
+			} catch (ConnectNodesException cnEx) {
+				JOptionPane.showMessageDialog(GraphPanel.this,
+						cnEx.getLocalizedMessage(),
+						ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"),
+						JOptionPane.ERROR_MESSAGE);
+				iLog("insert edge error",cnEx.getMessage());
+			}
+		}
+	}
+
+	private List<Edge> edges;
+	private List<Node> nodes;
+	private DiagramModelUpdater modelUpdater; 
+	private CollectionModel<Node,Edge> model;
+
+	private Grid grid;
+	private GraphToolbar toolbar;
+	private NodePopupMenu nodePopup;
+	private EdgePopupMenu edgePopup;
+
+	private double zoom;
+	private double gridSize;
+	private boolean hideGrid;
+	private boolean wasMoving;
+
+	private GraphElement top;
+	private GraphElement left;
+	private double minX;
+	private double minY;
+
+	private DiagramElement lastSelected;
+	private Edge moveLockedEdge;
+	private Set<DiagramElement> selectedElements;
+	private Set<Object> moveLockedElements;
+
+	private Point2D lastMousePoint;
+	private Point2D mouseDownPoint;
+	private Rectangle2D minBounds;
+	private int dragMode;
+
+	private int oldLazoSelectedNum;
+
+	/* button is not down, mouse motion will habe no effects */
+	private static final int DRAG_NONE = 0;
+	/* one or more nodes (and eventually some edges) have been selected, mouse motion will result in a translation */ 
+	private static final int DRAG_NODE = 1;
+	/* one edge has been selected, mouse motion will result in an edge bending */ 
+	private static final int DRAG_EDGE = 2;  
+	/* mouse button down but nothing selected, mouse motion will result in a lasso */
+	private static final int DRAG_LASSO = 3; // multiple selection 
+
+	private static final int GRID = 10;
+	private static final double EDGE_END_MIN_CLICK_DIST = 10;
+
+	public static final Color GRABBER_COLOR =  new Color(0,128,255);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphToolbar.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,231 @@
+/*  
+ 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/)
+
+ 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.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+
+/**
+   A Toolbar that contains node and edge prototype icons. By using the toolbar 
+   the user can create nodes and edges in the diagram out of clonation from the prototype.
+*/
+@SuppressWarnings("serial")
+public class GraphToolbar extends JToolBar {
+   /**
+      Constructs a tool bar with no icons.
+   */
+   public GraphToolbar(Diagram diagram){
+      /* creates icon for select button */
+      Icon icon = new Icon(){
+            public int getIconHeight() { return BUTTON_SIZE; }
+            public int getIconWidth() { return BUTTON_SIZE; }
+            public void paintIcon(Component c, Graphics g,
+               int x, int y){
+               Graphics2D g2 = (Graphics2D)g;
+               GraphPanel.drawGrabber(g2, x + OFFSET, y + OFFSET);
+               GraphPanel.drawGrabber(g2, x + OFFSET, y + BUTTON_SIZE - OFFSET);
+               GraphPanel.drawGrabber(g2, x + BUTTON_SIZE - OFFSET, y + OFFSET);
+               GraphPanel.drawGrabber(g2, x + BUTTON_SIZE - OFFSET, y + BUTTON_SIZE - OFFSET);
+            }
+         };
+      /* add selection button */
+      ResourceBundle resources = 
+         ResourceBundle.getBundle(EditorFrame.class.getName());
+      String text = resources.getString("grabber.text");
+      selectButton = new NodeButton(null,icon);
+      selectButton.setToolTipText(text);
+      nodeButtonsGroup = new ButtonGroup();
+      nodeButtonsGroup.add(selectButton);
+      add(selectButton);
+
+      /* add diagram buttons to the toolbar */
+      Node[] nodeTypes = diagram.getNodePrototypes();
+      for (int i = 0; i < nodeTypes.length; i++){
+         text = nodeTypes[i].getType();
+         add(nodeTypes[i], text );
+      }
+      
+      /* select the select-button as default */
+      nodeButtonsGroup.setSelected(selectButton.getModel(), true);
+
+      /* separate node buttons from edge buttons */ 
+      addSeparator();
+      
+      /* add diagram edges to the toolbar */
+      Edge[] edgeTypes = diagram.getEdgePrototypes();
+      for (int i = 0; i < edgeTypes.length; i++){
+         text = edgeTypes[i].getType();
+         add(edgeTypes[i], text );
+      }          
+   }
+
+   /**
+      Gets the node prototype that is associated with
+      the currently selected button
+      @return a Node or Edge prototype
+   */
+   public Node getSelectedTool() {
+	   @SuppressWarnings("rawtypes")
+	   Enumeration elements = nodeButtonsGroup.getElements();
+	   while (elements.hasMoreElements()) {
+		   NodeButton b = (NodeButton)elements.nextElement();
+		   if (b.isSelected()) {
+			   /* switch back to the select-button */
+			   nodeButtonsGroup.setSelected(selectButton.getModel(), true);
+			   return b.getNode();
+		   }
+	   }
+	   /* getting here means the selection button is selected */
+	   return null;
+   }
+
+   /**
+      Adds a node to the tool bar.
+      @param n the node to add
+      @param tip the tool tip
+   */
+   public void add(final Node n, String text){
+      Icon icon = new Icon(){
+            public int getIconHeight() { return BUTTON_SIZE; }
+            public int getIconWidth() { return BUTTON_SIZE; }
+            public void paintIcon(Component c, Graphics g,
+                  int x, int y){
+               double width = n.getBounds().getWidth();
+               double height = n.getBounds().getHeight();
+               Graphics2D g2 = (Graphics2D)g;
+               double scaleX = (BUTTON_SIZE - OFFSET)/ width;
+               double scaleY = (BUTTON_SIZE - OFFSET)/ height;
+               double scale = Math.min(scaleX, scaleY);
+
+               AffineTransform oldTransform = g2.getTransform();
+               g2.translate(x, y);
+               g2.translate(OFFSET/2*scaleX,OFFSET/2*scaleY);
+               g2.scale(scale, scale);
+               g2.setColor(Color.black);
+               n.draw(g2);
+               g2.setTransform(oldTransform);
+            }
+         };
+
+      NodeButton button = new NodeButton(n, icon);
+      button.setToolTipText(text);
+      
+      add(button);
+      nodeButtonsGroup.add(button);
+   }
+   
+   /**
+      Adds an edge to the tool bar.
+      @param n the node to add
+      @param tip the tool tip
+   */
+   public void add(final Edge e, String text){
+       Icon icon = new Icon(){
+            public int getIconHeight() { return BUTTON_SIZE; }
+            public int getIconWidth() { return BUTTON_SIZE; }
+            public void paintIcon(Component c, Graphics g,
+               int x, int y){
+               Graphics2D g2 = (Graphics2D)g;
+               /* create two points */
+               Point2D p = new Point2D.Double();
+               Point2D q = new Point2D.Double();
+               p.setLocation(OFFSET, OFFSET);
+               q.setLocation(BUTTON_SIZE - OFFSET, BUTTON_SIZE - OFFSET);
+               
+               Line2D line = new Line2D.Double(p,q);
+               Rectangle2D bounds = new Rectangle2D.Double();
+               bounds.add(line.getBounds2D());
+               
+               double width = bounds.getWidth();
+               double height = bounds.getHeight();
+               double scaleX = (BUTTON_SIZE - OFFSET)/ width;
+               double scaleY = (BUTTON_SIZE - OFFSET)/ height;
+               double scale = Math.min(scaleX, scaleY);
+
+               AffineTransform oldTransform = g2.getTransform();
+               g2.translate(x, y);
+               g2.scale(scale, scale);
+               g2.translate(Math.max((height - width) / 2, 0), Math.max((width - height) / 2, 0));
+                              
+               g2.setColor(Color.black);
+               g2.setStroke(e.getStyle().getStroke());
+               g2.draw(line);
+               g2.setTransform(oldTransform);
+            }
+         };
+      final JButton button = new JButton(icon);               
+      button.setToolTipText(text);
+      button.setFocusable(false);
+      
+      button.addActionListener(new ActionListener(){
+		@Override
+		public void actionPerformed(ActionEvent evt) {
+			edgeCreatedListener.edgeCreated((Edge)e.clone());
+		}});
+      add(button);
+   }
+   
+   public void addEdgeCreatedListener(EdgeCreatedListener edgeCreatedListener){
+	   this.edgeCreatedListener = edgeCreatedListener;
+   }
+   
+   private class NodeButton extends JToggleButton{
+	   public NodeButton(Node node, Icon icon){
+		   super(icon);
+		   setFocusable(false);
+		   this.node = node;
+	   }
+	   
+	   public Node getNode(){
+		   return node;
+	   }
+	   Node node;
+   }
+   
+   public interface EdgeCreatedListener {
+		void edgeCreated(Edge e);
+   }
+   
+   private ButtonGroup nodeButtonsGroup;
+   private EdgeCreatedListener edgeCreatedListener;
+   private NodeButton selectButton;
+
+   private static final int BUTTON_SIZE = 	30;
+   private static final int OFFSET = 5;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Grid.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,134 @@
+/*  
+ 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/)
+
+ 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.Color;
+import java.awt.Graphics2D;
+import java.awt.Stroke;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+
+/**
+   A grid to which nodes can be "snapped". The
+   snapping operation moves a point to the nearest grid point.
+*/
+public class Grid
+{
+   /**
+      Constructs a grid with no grid points.
+   */
+   public Grid()
+   {
+      setGrid(0, 0);
+   }
+   
+   /**
+      Sets the grid point distances in x- and y-direction
+      @param x the grid point distance in x-direction
+      @param y the grid point distance in y-direction
+   */
+   public void setGrid(double x, double y)
+   {
+      gridx = x;
+      gridy = y;
+   }
+   
+   /**
+      Draws this grid inside a rectangle.
+      @param g2 the graphics context
+      @param bounds the bounding rectangle
+   */
+   public void draw(Graphics2D g2, Rectangle2D bounds)
+   {
+      Color PALE_BLUE = new Color(0.9F, 0.8F, 0.9F);
+      Color oldColor = g2.getColor();
+      g2.setColor(PALE_BLUE);
+      Stroke oldStroke = g2.getStroke();
+      for (double x = bounds.getX(); x < bounds.getMaxX(); x += gridx)
+         g2.draw(new Line2D.Double(x, bounds.getY(), x, bounds.getMaxY()));
+      for (double y = bounds.getY(); y < bounds.getMaxY(); y += gridy)
+         g2.draw(new Line2D.Double(bounds.getX(), y, bounds.getMaxX(), y));
+      g2.setStroke(oldStroke);
+      g2.setColor(oldColor);
+   }
+
+   /**
+      Snaps a point to the nearest grid point
+      @param p the point to snap. After the call, the 
+      coordinates of p are changed so that p falls on the grid.
+   */
+   public void snap(Point2D p)
+   {
+      double x;
+      if (gridx == 0)
+         x = p.getX();
+      else
+         x = Math.round(p.getX() / gridx) * gridx;
+      double y;
+      if (gridy == 0)
+         y = p.getY();
+      else
+         y = Math.round(p.getY() / gridy) * gridy;
+         
+      p.setLocation(x, y);
+   }
+
+   /**
+      Snaps a rectangle to the nearest grid points
+      @param r the rectangle to snap. After the call, the 
+      coordinates of r are changed so that all of its corners
+      falls on the grid.
+   */
+   public void snap(RectangularShape r)
+   {
+      double x;
+      double w;
+      w = r.getWidth();
+      if (gridx == 0)
+      {
+         x = r.getX();
+      }
+      else
+      {
+         x = Math.round(r.getX() / gridx) * gridx;
+//         w = Math.ceil(r.getWidth() / (2 * gridx)) * (2 * gridx);
+      }
+      double y;
+      double h;
+      h = r.getHeight();
+      if (gridy == 0)
+      {
+         y = r.getY();
+      }
+      else
+      {
+         y = Math.round(r.getY() / gridy) * gridy;
+//         h = Math.ceil(r.getHeight() / (2 * gridy)) * (2 * gridy);
+      }
+         
+      r.setFrame(x, y, w, h);      
+   }
+
+   private double gridx;
+   private double gridy;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticKindle.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,217 @@
+/*  
+ 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.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.SwingUtilities;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
+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.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;
+
+/**
+ * 
+ * An instance of HapticListener for the diagram editor. By this class visual diagrams 
+ * can be manipulated by an haptic device.  
+ *
+ */
+public class HapticKindle extends HapticListener {
+	
+	public HapticKindle(){
+		super();
+		unselectRunnable = new Runnable(){
+			@Override
+			public void run(){
+				frame.selectHapticHighligh(null);
+			}
+		};
+		frameBackupRunnable = new Runnable(){
+			@Override
+			public void run(){
+				frame.backupOpenDiagrams();
+			}
+		};
+	}
+
+	public void setEditorFrame(EditorFrame frame){
+		this.frame = frame;
+	}
+	
+	@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;
+		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());
+			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());
+			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);
+				}
+			});
+			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;
+			}
+			
+			try {
+				SwingUtilities.invokeAndWait(new Runnable(){
+					@Override
+					public void run(){
+						if(moveSelectedElement instanceof Node){
+							Node n = (Node)moveSelectedElement;
+							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);
+
+							StringBuilder builder = new StringBuilder();
+							builder.append(DiagramElement.toLogString(n)).append(" ").append(p.getX())
+							.append(' ').append(p.getY());
+							iLog("move node start",builder.toString());
+							builder = new StringBuilder();
+							builder.append(DiagramElement.toLogString(n)).append(' ')
+							.append(x).append(' ').append(y);
+
+							iLog("move node end",builder.toString());
+							
+						}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);
+							iLog("bend edge start",builder.toString());
+							builder = new StringBuilder();
+							builder.append(DiagramElement.toLogString(e)).append(' ')
+							.append(x).append(' ').append(y);
+							iLog("bend edge end",builder.toString());
+							
+						}
+					}
+				});
+			} 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());
+			break;
+		case PLAY_SOUND :
+			switch(HapticListenerCommand.Sound.fromInt(ID) ){
+			case MAGNET_OFF : 
+				SoundFactory.getInstance().play(SoundEvent.MAGNET_OFF);
+				iLog("sticky mode off","");
+				break;
+			case MAGNET_ON : 
+				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 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");
+			break;
+		}
+	}
+	
+	private void iLog(String action, String args){
+		InteractionLog.log(INTERACTION_LOG_SOURCE,action,args);
+	}
+	
+	private Runnable unselectRunnable;
+	private Runnable frameBackupRunnable;
+	private static String INTERACTION_LOG_SOURCE = "HAPTIC"; 
+	private EditorFrame frame;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticTrigger.java	Fri Dec 16 17:35:51 2011 +0000
@@ -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.gui;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.haptics.HapticsFactory;
+
+/**
+ * A CollectionListener that updates the haptic scene upon insertion deletion and 
+ * movement of nodes and edges in the diagram.
+ * 
+ *
+ */
+public class HapticTrigger implements CollectionListener {
+
+	@Override
+	public void elementInserted(CollectionEvent evt) {
+		if(evt.getDiagramElement() instanceof Node){
+			Node n = (Node)evt.getDiagramElement();
+			HapticsFactory.getInstance().addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n));
+		}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());
+		}
+	}
+
+	@Override
+	public void elementTakenOut(CollectionEvent evt) {
+		if(evt.getDiagramElement() instanceof Node){
+			Node n = (Node)evt.getDiagramElement();
+			HapticsFactory.getInstance().removeNode(System.identityHashCode(n));
+		}else{//edge
+			Edge e = (Edge)evt.getDiagramElement();
+			HapticsFactory.getInstance().removeEdge(System.identityHashCode(e));
+		}
+	}
+
+	@Override
+	public void elementChanged(ElementChangedEvent evt) {
+		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());
+			}else{
+				Node n = (Node)evt.getDiagramElement();		
+				HapticsFactory.getInstance().moveNode(
+						n.getBounds().getCenterX(),
+						n.getBounds().getCenterY(),
+						System.identityHashCode(n)
+				);
+			}
+		}
+	}
+	   
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/LineStyle.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,53 @@
+/*  
+ 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.BasicStroke;
+import java.awt.Stroke;
+
+/**
+ * Defines the possible values for the stroke of lines painted on the graph when drawing an {@link Edge}. 
+ * It can be <i>Solid</i>, <i>Dotted</i> or <i>Dashed</i>.
+ *
+ */
+public enum LineStyle {
+	Solid(new BasicStroke()),
+	Dotted(new BasicStroke(1.0f, 
+			BasicStroke.CAP_ROUND, 
+			BasicStroke.JOIN_ROUND, 
+			0.0f, 
+			new float[]{1.0f,3.0f}, 
+			0.0f)),
+	Dashed(new BasicStroke(1.0f, 
+			BasicStroke.CAP_ROUND, 
+			BasicStroke.JOIN_ROUND, 
+			0.0f, 
+			new float[]{5.0f,5.0f}, 
+			0.0f));
+	
+	private LineStyle(BasicStroke stroke){
+		this.stroke = stroke;
+	}
+
+	public Stroke getStroke(){
+		return stroke;
+	}
+	
+	private Stroke stroke;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Lock.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,36 @@
+/*  
+ 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;
+
+/**
+ * 
+ * An enum that defines the possible locks that can be granted on a shared diagram.  
+ *
+ */
+public enum Lock {
+	DELETE,
+	NAME,
+	PROPERTIES,
+	EDGE_END,
+	MOVE,
+	NOTES,
+	BOOKMARK,
+	MUST_EXIST,
+	NONE
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/LoopComboBox.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,89 @@
+/*  
+ 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.event.ItemEvent;
+import java.awt.event.KeyEvent;
+import java.util.Vector;
+
+import javax.swing.ComboBoxModel;
+import javax.swing.JComboBox;
+
+/**
+ * A ComboBox component which overrides the default behaviour when selecting items by the keyboard
+ * up and down arrow keys. When the top is reached, if the user presses the up arrow key  
+ * instead of blocking on the first item, the LoopComboBox loops forward to the last item. Likewise,
+ * when the bottom is reached and the user presses the down arrow key the LoopComboBox 
+ * loops back to the first item.  
+ *
+ */
+@SuppressWarnings("serial")
+public class LoopComboBox extends JComboBox {
+	public LoopComboBox(){
+		super();
+	}
+	
+	public LoopComboBox(Object[] items){
+		super(items);
+	}
+	
+	public LoopComboBox(ComboBoxModel aModel){
+		super(aModel);
+	}
+	
+	public LoopComboBox(Vector<?> items){
+		super(items);
+	}
+	
+	@Override
+	public void	processKeyEvent(KeyEvent e) {
+		if(dataModel.getSize() == 0){
+			super.processKeyEvent(e);
+		}else{
+			if(e.getKeyCode() == KeyEvent.VK_DOWN 
+					&& e.getID()==KeyEvent.KEY_PRESSED 
+					&& getSelectedIndex() == getItemCount()-1){
+				setSelectedIndex(0);
+				if(getItemCount() == 1)
+					fireOneItemStateChanged();
+			}else if(e.getKeyCode() == KeyEvent.VK_UP 
+					&& e.getID()==KeyEvent.KEY_PRESSED 
+					&& getSelectedIndex() == 0){
+				setSelectedIndex(getItemCount()-1);
+				if(getItemCount() == 1)
+					fireOneItemStateChanged();
+			}else
+				super.processKeyEvent(e);
+		}
+	}
+	
+	/**
+	 * when the comboBox has only one item the ItemStateChanged listeners ain't fired by default.
+	 * This behaviour has to be forced in order to have the item label to be spoken out by  
+	 * the narrator, in spite of the item number .  
+	 */
+	private void fireOneItemStateChanged(){
+		fireItemStateChanged(new ItemEvent(
+				this,
+				ItemEvent.ITEM_STATE_CHANGED,
+				getSelectedItem(),
+				ItemEvent.SELECTED
+		));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/LoopSpinnerNumberModel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,60 @@
+/*  
+ 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 javax.swing.SpinnerNumberModel;
+
+/**
+ * A SpinnerNumberModel which overrides the default behaviour when selecting items by the keyboard
+ * up and down arrow keys.
+ * When the maximum value is reached, a call to getNextValue() 
+ * will return the minimum value, instead of returning null. Likewise,
+ * when the minimum value is reached, a call to getPreviousValue() will return the maximum value 
+ * instead of returning null. 
+ * 
+ *
+ */
+@SuppressWarnings("serial")
+public class LoopSpinnerNumberModel extends SpinnerNumberModel {
+	public LoopSpinnerNumberModel(int value, int minimum, int maximum){
+		super(value,minimum,maximum,1);
+	}
+	
+	@Override
+	public Object getNextValue(){
+		Object nextValue = super.getNextValue();
+		if(nextValue == null)
+			return getMinimum();
+		else
+			return nextValue;
+	}
+	
+	@Override
+	public Object getPreviousValue(){
+		Object previousValue = super.getPreviousValue();
+		if(previousValue == null)
+			return getMaximum();
+		else 
+			return previousValue;
+	}
+	
+	public Object getValue(){
+		return super.getValue();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/ModifierEditorDialog.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,138 @@
+/*  
+ 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.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities;
+
+/**
+ * 
+ * A dialog showing a list of checkboxes. By selecting the checkboxes the user can choose
+ * which modifiers are assigned to a property value. 
+ *
+ */
+@SuppressWarnings("serial")
+public class ModifierEditorDialog extends JDialog {
+	
+	private ModifierEditorDialog(JDialog parent, List<String> modifierTypes, Set<Integer> modifierIndexes){
+		super(parent, resources.getString("dialog.modifier_editor.title"), true);
+		init(modifierTypes, modifierIndexes);
+	}
+	
+	private ModifierEditorDialog(Frame parent, List<String> modifierTypes, Set<Integer> modifierIndexes){
+		super(parent, resources.getString("dialog.modifier_editor.title"), true);
+		init(modifierTypes, modifierIndexes);
+	}
+	
+	private void init(List<String> modifierTypes, Set<Integer> modifierIndexes){	
+		listenerManager = new ListenerManager();
+		createComponents();
+		
+		panel.setLayout(new GridBagLayout());
+		
+		checkBoxes = new JCheckBox[modifierTypes.size()]; 
+		GridBagUtilities gridBagUtils = new GridBagUtilities(); 
+		int i = 0;
+		for(String modifierType : modifierTypes){
+			panel.add(new JLabel(modifierType), gridBagUtils.label());
+			checkBoxes[i] = new JCheckBox();
+			if(modifierIndexes.contains(i))
+				checkBoxes[i].setSelected(true);
+			panel.add(checkBoxes[i],gridBagUtils.field());
+			i++;
+		}
+		
+		buttonPanel.add(okButton);
+		buttonPanel.add(cancelButton);
+		okButton.addActionListener(listenerManager);
+		cancelButton.addActionListener(listenerManager);
+		panel.add(buttonPanel,gridBagUtils.all());
+		
+		setContentPane(panel);
+		setResizable(false);
+		pack();
+	}
+	
+	public static Set<Integer> showDialog(JDialog parent, List<String> modifierTypes, Set<Integer> modifiers){
+		ModifierEditorDialog.modifiers = modifiers;
+		dialog = new ModifierEditorDialog(parent, modifierTypes, modifiers);
+		dialog.setLocationRelativeTo(parent);
+		dialog.setVisible(true);
+		return ModifierEditorDialog.modifiers;
+		
+	}
+	
+	public static Set<Integer> showDialog(Frame parent, List<String> modifierTypes, Set<Integer> modifiers){
+		ModifierEditorDialog.modifiers = modifiers;
+		dialog = new ModifierEditorDialog(parent, modifierTypes, modifiers);
+		dialog.setLocationRelativeTo(parent);
+		dialog.setVisible(true);
+		return ModifierEditorDialog.modifiers;
+		
+	}
+	
+	private void createComponents(){
+		panel = new JPanel();
+		buttonPanel = new JPanel();
+		okButton = new JButton(resources.getString("dialog.ok_button"));
+		cancelButton = new JButton(resources.getString("dialog.cancel_button"));
+	}
+	
+	private JPanel panel;
+	private JPanel buttonPanel;
+	private JButton okButton;
+	private JButton cancelButton;
+	private JCheckBox[] checkBoxes;
+	private ListenerManager listenerManager; 
+	
+	private static Set<Integer> modifiers;
+	private static ModifierEditorDialog dialog;
+	private static ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+	
+	private class ListenerManager implements ActionListener {
+		@Override
+		public void actionPerformed(ActionEvent evt) {
+				Object source = evt.getSource();
+				if(source.equals(okButton)){
+					for(int i=0;i<checkBoxes.length;i++)
+						if(checkBoxes[i].isSelected()) 
+							modifiers.add(i);
+						else 
+							modifiers.remove(i);
+					dispose();
+				}else if(source.equals(cancelButton)){
+					dispose();
+				}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,338 @@
+/*  
+ 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/)
+
+ 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.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+
+/**
+ * An node in a graph. Node objects are used in a GraphPanel to render diagram nodes visually. 
+ * Node objects are used in the tree representation of the diagram as well, as they're
+ * subclasses of {@link DiagramNode}    
+ * 
+ */
+@SuppressWarnings("serial")
+public abstract class Node extends DiagramNode implements GraphElement{
+
+	public Node(String type, NodeProperties properties){
+		super(type,properties);
+		internalNodes = new ArrayList<Node>();
+		attachedEdges = new ArrayList<Edge>();
+		externalNode = null;
+	}
+
+	/* --- DiagramNode abstract methods implementation --- */
+	@Override
+	public int getEdgesNum(){
+		return attachedEdges.size();
+	}
+
+	@Override
+	public Edge getEdgeAt(int index){
+		return attachedEdges.get(index);
+	}
+	
+	@Override
+	public boolean addEdge(DiagramEdge e){
+		return attachedEdges.add((Edge)e);
+	}
+
+	@Override
+	public boolean removeEdge(DiagramEdge e){
+		return attachedEdges.remove((Edge)e);
+	}
+
+	@Override
+	public Node getExternalNode(){
+		return externalNode; 
+	}
+
+	@Override
+	public void setExternalNode(DiagramNode node){
+		externalNode = (Node)node;
+	}
+
+	@Override
+	public  Node getInternalNodeAt(int i){
+		return internalNodes.get(i); 
+	}
+	
+	@Override
+	public int getInternalNodesNum(){
+		return internalNodes.size();
+	}
+
+	@Override
+	public void addInternalNode(DiagramNode node){
+		internalNodes.add((Node)node);
+	}
+
+	@Override
+	public void removeInternalNode(DiagramNode node){
+		if (node.getExternalNode() != this) 
+			return;
+		internalNodes.remove(node);
+		node.setExternalNode(null);
+	}
+
+	@Override
+	public void stopMove(){
+		notifyChange(new ElementChangedEvent(this,this,"stop_move"));
+		/* 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();
+		}
+	}
+
+	/**
+	 * 	Translates the node by a given amount
+	 *	@param p the point we are translating from
+	 *	@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){
+		translateImplementation( p, dx, dy);
+		notifyChange(new ElementChangedEvent(this, this, "translate"));
+	}
+
+	protected void translateImplementation(Point2D p , double dx, double dy){
+		for(int i=0; i< getInternalNodesNum();i++){
+			getInternalNodeAt(i).translate(p, dx, dy);
+		}
+	}
+
+	/**
+	 *	Tests whether the node contains a point.
+	 *	@param aPoint the point to test
+	 *	@return true if this node contains aPoint
+	 */
+	public abstract boolean contains(Point2D aPoint);
+
+	/**
+	 * Get the bounding rectangle of the shape of this node
+	 * @return the bounding rectangle
+	 */
+	@Override
+	public abstract Rectangle2D getBounds();
+
+	@Override
+	public void startMove(Point2D p){
+		/* useless, here just to comply with the GraphElement interface */
+	}
+
+	public abstract Point2D getConnectionPoint(Direction d);
+
+	@Override
+	public void draw(Graphics2D g2){
+		if(!"".equals(getNotes())){
+			Rectangle2D bounds = getBounds();
+			Color oldColor = g2.getColor();
+			g2.setColor(GraphPanel.GRABBER_COLOR);
+			g2.fill(new Rectangle2D.Double(bounds.getX() - MARKER_SIZE / 2, bounds.getY() - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE));
+			g2.setColor(oldColor);
+		}
+	}
+
+	public abstract Shape getShape();
+
+	public void encode(Document doc, Element parent){
+		parent.setAttribute(PersistenceManager.NAME,getName());
+
+		Element positionTag = doc.createElement(PersistenceManager.POSITION);
+		Rectangle2D bounds = getBounds();
+		positionTag.setAttribute(PersistenceManager.X, String.valueOf(bounds.getX()));
+		positionTag.setAttribute(PersistenceManager.Y, String.valueOf(bounds.getY()));
+		parent.appendChild(positionTag);
+
+		if(getProperties().isEmpty())
+			return;
+
+		Element propertiesTag = doc.createElement(PersistenceManager.PROPERTIES);
+		parent.appendChild(propertiesTag);
+		for(String type : getProperties().getTypes()){
+			List<String> values = getProperties().getValues(type);
+			if(values.isEmpty())
+				continue;
+			Element propertyTag = doc.createElement(PersistenceManager.PROPERTY);
+			propertiesTag.appendChild(propertyTag);
+
+			Element typeTag = doc.createElement(PersistenceManager.TYPE);
+			typeTag.appendChild(doc.createTextNode(type));
+			propertyTag.appendChild(typeTag);
+
+			int index = 0;
+			for(String value : values){
+				Element elementTag = doc.createElement(PersistenceManager.ELEMENT);
+				propertyTag.appendChild(elementTag);
+
+				Element valueTag = doc.createElement(PersistenceManager.VALUE);
+				valueTag.appendChild(doc.createTextNode(value));
+				elementTag.appendChild(valueTag);
+
+
+				Set<Integer> modifierIndexes = getProperties().getModifiers(type).getIndexes(index);
+				if(!modifierIndexes.isEmpty()){
+					Element modifiersTag = doc.createElement(PersistenceManager.MODIFIERS);
+					StringBuilder builder = new StringBuilder();
+					for(Integer i : modifierIndexes )
+						builder.append(i).append(' ');
+					builder.deleteCharAt(builder.length()-1);//remove last space
+					modifiersTag.appendChild(doc.createTextNode(builder.toString()));
+					elementTag.appendChild(modifiersTag);
+				}
+				index++;
+			}
+		}
+	}
+
+	public void decode(Document doc, Element nodeTag) throws IOException{
+		setName(nodeTag.getAttribute(PersistenceManager.NAME));
+		try{
+			setId(Integer.parseInt(nodeTag.getAttribute(PersistenceManager.ID)));
+		}catch(NumberFormatException nfe){
+			throw new IOException();
+		}
+
+		if(nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
+			throw new IOException();
+		Element positionTag = (Element)nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
+		double dx,dy;
+		try{
+			dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
+			dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
+		}catch(NumberFormatException nfe){
+			throw new IOException();
+		}
+		Rectangle2D bounds = getBounds();
+		translate(new Point2D.Double(0,0), dx - bounds.getX(), dy - bounds.getY());
+
+		NodeList propList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
+		NodeProperties properties = getProperties();
+		for(int j=0; j<propList.getLength();j++){
+			Element propertyTag = (Element)propList.item(j);
+
+			if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
+				throw new IOException();
+			Element pTypeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+			String propertyType = pTypeTag.getTextContent();
+
+			/* scan all the <Element> of the current <Property>*/
+			NodeList elemValueList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
+			for(int h=0; h<elemValueList.getLength(); h++){
+
+				Element elemTag = (Element)elemValueList.item(h);
+				/* get the <value> */
+				if(elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
+					throw new IOException();
+				Element valueTag = (Element)elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
+				String value = valueTag.getTextContent();
+
+				/* <modifiers>. need to go back on the prototypes because the content of <modifier> is a list */
+				/* of int index pointing to the modifiers type, defined just in the prototypes                */
+				Element prototypesTag = (Element)doc.getElementsByTagName(PersistenceManager.PROTOTYPES).item(0);
+				Modifiers modifiers = null;
+				try {
+					modifiers = properties.getModifiers(propertyType);
+				}catch(IllegalArgumentException iae){
+					throw new IOException(iae);
+				}
+				if(!modifiers.isNull()){
+					Element modifiersTag = (Element)((Element)elemValueList.item(h)).getElementsByTagName(PersistenceManager.MODIFIERS).item(0);
+					if(modifiersTag != null){ //else there are no modifiers specified for this property value
+						Set<Integer> indexesToAdd = new LinkedHashSet<Integer>();
+						String indexesString = modifiersTag.getTextContent();
+						String[] indexes = indexesString.split(" ");
+						for(String s : indexes){
+							try{
+								int index = Integer.parseInt(s);
+								NodeList templatePropList = prototypesTag.getElementsByTagName(PersistenceManager.PROPERTY);
+								String modifiersType = null;
+								/* look at the property prototypes to see which modifier the index is referring to.      *  
+								 * The index is in fact the id attribute of the <Modifier> tag in the prototypes section */
+								for(int k=0; k<templatePropList.getLength();k++){
+									Element prototypePropTag = (Element)templatePropList.item(k);
+									Element prototypePropTypeTag = (Element)prototypePropTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+	
+									if(propertyType.equals(prototypePropTypeTag.getTextContent())){
+										NodeList proptotypeModifierList = prototypePropTag.getElementsByTagName(PersistenceManager.MODIFIER);
+										for(int m = 0 ; m<proptotypeModifierList.getLength();m++){
+											if(index == Integer.parseInt(((Element)proptotypeModifierList.item(m)).getAttribute(PersistenceManager.ID))){
+												Element modifierTypeTag = (Element)((Element)proptotypeModifierList.item(m)).getElementsByTagName(PersistenceManager.TYPE).item(0);
+												modifiersType = modifierTypeTag.getTextContent();
+											}
+										}
+									}
+								}
+								if(modifiersType == null) // the index must point to a valid modifier's id
+									throw new IOException();
+								indexesToAdd.add(Integer.valueOf(modifiers.getTypes().indexOf(modifiersType)));
+							}catch(NumberFormatException nfe){
+								throw new IOException(nfe);
+							}
+						}
+						addProperty(propertyType, value);//whether propertyType actually exist in the prototypes has been already checked  
+						setModifierIndexes(propertyType, h, indexesToAdd);
+					}else
+						addProperty(propertyType, value);
+				}else
+					addProperty(propertyType, value);
+			}
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	public Object clone(){
+		Node clone = (Node)super.clone();
+		clone.internalNodes = (ArrayList<Node>) internalNodes.clone();
+		clone.externalNode = null;
+		clone.attachedEdges = (ArrayList<Edge>) attachedEdges.clone(); 
+		return clone;
+	}
+
+	protected ArrayList<Node> internalNodes;
+	protected Node externalNode;
+	protected ArrayList<Edge> attachedEdges;
+
+	private final int MARKER_SIZE = 7;
+	protected static final Color SHADOW_COLOR = Color.LIGHT_GRAY;
+	public static final int SHADOW_GAP = 2;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/NodePopupMenu.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,112 @@
+/*  
+ 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);
+				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());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/NodePopupMenu.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,8 @@
+
+
+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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyEditorDialog.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,195 @@
+/*  
+ 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.Dimension;
+import java.awt.Frame;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
+import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities;
+
+/**
+ * A Dialog for editing the {@link NodeProperties} of a diagram node.  
+ *
+ */
+@SuppressWarnings("serial")
+public class PropertyEditorDialog extends JDialog {
+	
+	private PropertyEditorDialog(Frame parent){
+		super(parent, resources.getString("dialog.property_editor.title") , true);
+		setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+		
+		addWindowListener(new WindowAdapter(){
+			@Override
+			public void windowClosing(WindowEvent event){
+				result = null;
+				dispose();
+			}
+		});
+		
+		listenerManager = new ListenerManager();
+		
+		createComponents();
+		setContentPane(scrollPane);
+		
+		panel.setLayout(new GridBagLayout());
+		
+		GridBagUtilities gridBagUtils = new GridBagUtilities(); 
+		panel.add(topSeparator,gridBagUtils.all());
+		
+		int i = 0;
+		for(String type : properties.getTypes()){
+			PropertyPanel propertyPanel = new PropertyPanel(type, properties.getValues(type), properties.getModifiers(type));
+			panel.add(propertyPanel,gridBagUtils.all());
+			propertyPanels[i++] = propertyPanel;
+		}
+		
+		if(!properties.getTypes().isEmpty())
+			panel.add(bottomSeparator, gridBagUtils.all());
+		buttonPanel.add(okButton);
+		buttonPanel.add(cancelButton);
+		panel.add(buttonPanel, gridBagUtils.all());
+		
+		okButton.addActionListener(listenerManager);
+		cancelButton.addActionListener(listenerManager);
+		pack();
+	}
+	
+	public static NodeProperties showDialog(Frame parent, NodeProperties properties){
+		if(properties == null)
+			throw new IllegalArgumentException(resources.getString("dialog.property_editor.error.property_null"));		
+		PropertyEditorDialog.properties = properties;
+		dialog = new PropertyEditorDialog(parent);
+		dialog.setLocationRelativeTo(parent);
+		dialog.setVisible(true);
+		return result;
+	}
+	
+	private void createComponents(){
+		panel = new JPanel();
+		buttonPanel = new JPanel();
+		propertyPanels = new PropertyPanel[PropertyEditorDialog.properties.getTypes().size()];
+		scrollPane  = new JScrollPane(
+				panel,
+				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
+				);
+		bottomSeparator = new JSeparator();
+		topSeparator = new JSeparator();
+		okButton = new JButton(resources.getString("dialog.ok_button"));
+		cancelButton = new JButton(resources.getString("dialog.cancel_button"));
+	}
+	
+	private JPanel panel;
+	private JPanel buttonPanel;
+	private PropertyPanel[] propertyPanels;
+	private JScrollPane scrollPane;
+	private JSeparator topSeparator;
+	private JSeparator bottomSeparator;
+	private JButton okButton;
+	private JButton cancelButton;
+	private ListenerManager listenerManager;
+	
+	private static PropertyEditorDialog dialog;
+	private static NodeProperties properties;
+	private static NodeProperties result;
+	private static ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+	
+	private class PropertyPanel extends JPanel{
+		public PropertyPanel(String propertyType, List<String> values, final Modifiers modifiers ){
+			setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
+			model = new PropertyTableModel(propertyType, values, modifiers);
+			if(modifiers != null)
+				for(int i=0; i< values.size(); i++){
+					model.setIndexesAt(i, modifiers.getIndexes(i));
+				}
+			table = new JTable(model);
+			table.setPreferredScrollableViewportSize(new Dimension(250, 70));
+	        table.setFillsViewportHeight(true);
+	        
+			add(new JScrollPane(table));
+			/* we can edit modifiers only if one or more modifier types have been defined for this property type */
+			if(!modifiers.isNull()){
+				editModifiers = new JButton(resources.getString("dialog.property_editor.edit_modifiers_button"));
+				editModifiers.setAlignmentX(RIGHT_ALIGNMENT);
+				add(editModifiers);
+				editModifiers.addActionListener(new ActionListener(){
+					@Override
+					public void actionPerformed(ActionEvent arg0) {
+						int row = table.getSelectedRow();
+						if((row == -1)||(row == model.getRowCount()-1))
+							return;
+						Set<Integer> indexes;
+						
+						indexes = ModifierEditorDialog.showDialog(PropertyEditorDialog.this, modifiers.getTypes(), model.getIndexesAt(row));
+						model.setIndexesAt(row, indexes);
+					}
+				});
+			}
+			add(Box.createRigidArea(new Dimension(0,5)));
+		}
+		
+		JTable table;
+		PropertyTableModel model;
+		JButton editModifiers;
+	}
+	
+	private class ListenerManager implements ActionListener{
+		@Override
+		public void actionPerformed(ActionEvent evt) {
+			Object source = evt.getSource();
+			if(source.equals(okButton)){
+				for(int i=0; i<propertyPanels.length;i++){
+					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.equals("")){
+							properties.addValue(model.getColumnName(0),value , model.getIndexesAt(j));
+						}
+					}
+				}
+				result = properties;
+				dispose();
+			}else{
+				result = null;
+				dispose();
+			}
+		}
+ 	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyTableModel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,141 @@
+/*  
+ 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.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+
+import javax.swing.table.AbstractTableModel;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
+
+
+/**
+ * 
+ * A table model containing the property values currently edited and the modifiers assigned to them
+ * in the form of an array of indexes pointing to the modifiers types  
+ */
+@SuppressWarnings("serial")
+public class PropertyTableModel extends AbstractTableModel {
+
+	public PropertyTableModel(String propertyType, List<String> values, Modifiers modifiers ){
+		data = new ArrayList<ModifierString>();
+		for(int i = 0; i< values.size();i++){
+			String value = values.get(i);
+			data.add(new ModifierString(value, (modifiers == null) ? null : modifiers.getIndexes(i)));
+		}
+		data.add(new ModifierString(null));
+		columnName = propertyType;
+	}
+	
+	@Override
+	public int getColumnCount() {
+		return 1;
+	}
+
+	@Override
+	public int getRowCount() {
+		return data.size();
+	}
+
+	@Override
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		return data.get(rowIndex);
+	}
+	
+	@Override
+	public String getColumnName(int column){
+		 return columnName;
+	}
+	
+	@Override
+	public void setValueAt(Object value, int rowIndex, int columnIndex){
+		/* we filled up the last row, create another  one */
+		if((rowIndex == data.size()-1)&&(!value.toString().equals(""))){
+			data.add(new ModifierString(null));
+			data.get(rowIndex).value = value.toString();
+			fireTableRowsInserted(data.size()-1, data.size()-1);
+			fireTableCellUpdated(rowIndex,columnIndex);
+		}else if((rowIndex != data.size()-1)&&(value.toString().equals(""))){
+			data.remove(rowIndex);
+			fireTableRowsDeleted(rowIndex,rowIndex);
+		}else {
+			data.get(rowIndex).value = value.toString();
+			fireTableCellUpdated(rowIndex,columnIndex);
+		}
+	}
+	
+	@Override
+	public boolean isCellEditable(int rowIndex, int columnIndex){
+		return true;
+	}
+	
+	public Set<Integer> getIndexesAt(int row){
+		return data.get(row).modifierIndexes;
+	}
+	
+	public void setIndexesAt(int row, Set<Integer> indexes){
+		data.get(row).modifierIndexes = new HashSet<Integer>();
+		data.get(row).modifierIndexes.addAll(indexes);
+	}
+	
+	public void setIndexesAt(int row, Integer[] indexes){
+		data.get(row).modifierIndexes = new HashSet<Integer>();
+		for(int i=0; i<indexes.length; i++)
+			data.get(row).modifierIndexes.add(indexes[i]);
+	}
+	
+	List<ModifierString> data;
+	String columnName;
+	
+	private class ModifierString  {
+		ModifierString(String value, Set<Integer> s){
+			this.value = value;
+			modifierIndexes = new HashSet<Integer>();
+			if(s != null){
+					modifierIndexes.addAll(s);
+			}
+		}
+		
+		ModifierString(Set<Integer> s){
+			this("",s);
+		}
+		
+		@Override
+		public boolean equals(Object o){
+			return value.equals(o);
+		}
+		
+		@Override
+		public int hashCode(){
+			return value.hashCode();
+		}
+		
+		@Override
+		public String toString(){
+			return value;
+		}
+		
+		String value;
+		Set<Integer> modifierIndexes;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/ResourceFactory.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,200 @@
+/*  
+ 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/)
+
+ 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.Image;
+import java.awt.event.ActionListener;
+import java.beans.EventHandler;
+import java.net.URL;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
+
+/**
+ * A factory class for swing components creation support  
+ *
+ */
+public class ResourceFactory{
+   public ResourceFactory(ResourceBundle bundle){
+      this.bundle = bundle;
+   }
+
+   public JMenuBar createMenuBar(){
+	   return SpeechMenuFactory.getMenuBar();
+   }
+   
+   public JMenuItem createMenuItem(String prefix, 
+      Object target, String methodName){
+      return createMenuItem(prefix,
+         (ActionListener) EventHandler.create(
+            ActionListener.class, target, methodName));
+   }
+
+   public JMenuItem createMenuItem(String prefix, 
+      ActionListener listener){
+      String text = bundle.getString(prefix + ".text");
+      JMenuItem menuItem = SpeechMenuFactory.getMenuItem(text);
+      return configure(menuItem, prefix, listener);
+   }
+   
+   public JMenuItem createMenuItemFromLabel(String label, ActionListener listener){
+	   JMenuItem menuItem = SpeechMenuFactory.getMenuItem(label);
+	   menuItem.addActionListener(listener);
+	   return menuItem; 
+   }
+
+   public JMenuItem createCheckBoxMenuItem(String prefix, 
+      ActionListener listener){
+      String text = bundle.getString(prefix + ".text");
+      JMenuItem menuItem = SpeechMenuFactory.getJCheckBoxMenuItem(text);
+      return configure(menuItem, prefix, listener);
+   }
+
+   public JMenuItem configure(JMenuItem menuItem, 
+      String prefix, ActionListener listener){      
+      menuItem.addActionListener(listener);
+      try{
+         String mnemonic = bundle.getString(prefix + ".mnemonic");
+         menuItem.setMnemonic(mnemonic.charAt(0));
+      }catch (MissingResourceException exception){
+         // ok not to set mnemonic
+      }
+
+      try{
+         String accelerator = bundle.getString(prefix + ".accelerator");
+         menuItem.setAccelerator(KeyStroke.getKeyStroke(accelerator));
+      }catch (MissingResourceException exception){
+         // ok not to set accelerator
+      }
+
+      try{
+         String tooltip = bundle.getString(prefix + ".tooltip");
+         menuItem.setToolTipText(tooltip);         
+      }catch (MissingResourceException exception){
+         // ok not to set tooltip
+      }
+      return menuItem;
+   }
+   
+   public JMenu createMenu(String prefix){
+      String text = bundle.getString(prefix + ".text");
+      JMenu menu = SpeechMenuFactory.getMenu(text);
+      try{
+         String mnemonic = bundle.getString(prefix + ".mnemonic");
+         menu.setMnemonic(mnemonic.charAt(0));
+      }catch (MissingResourceException exception){
+         // ok not to set mnemonic
+      }
+
+      try{
+         String tooltip = bundle.getString(prefix + ".tooltip");
+         menu.setToolTipText(tooltip);         
+      }
+      catch (MissingResourceException exception)
+      {
+         // ok not to set tooltip
+      }
+      return menu;
+   }
+      
+   public JButton createButton(String prefix) {
+      String text = bundle.getString(prefix + ".text");
+      JButton button = new JButton(text);
+      try {
+         String mnemonic = bundle.getString(prefix + ".mnemonic");
+         button.setMnemonic(mnemonic.charAt(0));
+      }
+      catch (MissingResourceException exception) {
+         // ok not to set mnemonic
+      }
+
+      try {
+         String tooltip = bundle.getString(prefix + ".tooltip");
+         button.setToolTipText(tooltip);         
+      }
+      catch (MissingResourceException exception) {
+         // ok not to set tooltip
+      }
+      return button;
+   }
+   
+   
+   public Action configureAction(String prefix, Action action)
+   {
+      try
+      {
+         String text = bundle.getString(prefix + ".text");
+         action.putValue(Action.NAME, text);
+      }
+      catch (MissingResourceException exception)
+      {
+         // ok not to set name
+      }
+
+      try
+      {
+         String mnemonic = bundle.getString(prefix + ".mnemonic");
+         action.putValue(Action.MNEMONIC_KEY, new Integer(mnemonic.charAt(0)));
+      }
+      catch (MissingResourceException exception)
+      {
+         // ok not to set mnemonic
+      }
+
+      try {
+         String accelerator = bundle.getString(prefix + ".accelerator");
+         action.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(accelerator));
+      }
+      catch (MissingResourceException exception){
+         // ok not to set accelerator
+      }
+
+      try
+      {
+         String tooltip = bundle.getString(prefix + ".tooltip");
+         action.putValue(Action.SHORT_DESCRIPTION, tooltip);         
+      }
+      catch (MissingResourceException exception)
+      {
+         // ok not to set tooltip
+      }
+      return action;
+   }
+   
+   public static class ImageFactory{ 
+	   public Image getImage(String path){
+		   URL url = getClass().getResource(path);
+		   if(url != null)
+				return new ImageIcon(url).getImage();
+		   else
+			   return null;
+	   }
+   }
+
+   private ResourceBundle bundle;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechLogDialog.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,100 @@
+/*  
+ 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,184 @@
+/*  
+ 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.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.MenuElement;
+import javax.swing.MenuSelectionManager;
+
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+
+/**
+ * 
+ * This class provides a version of JMenuItem, JCheckBox, and JMenu which emit an "error" sound when 
+ * they're disabled and the user tries to use it by an accelerator
+ */
+@SuppressWarnings("serial")
+public class SpeechMenuFactory extends JMenu {
+
+	/* implements the singleton pattern and keeps a static reference to the menuBar used bu JMenuItems and JMenus */
+	public static JMenuBar getMenuBar(){
+		if(menuBar == null){
+			menuBar = new JMenuBar(){
+				@Override
+				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");
+					}
+				}
+			};
+		}
+		return menuBar;
+	}
+	
+	public static JMenuItem getMenuItem(String text){
+		return new SpeechMenuItem(text);
+	}
+	
+	public static JMenu getMenu(String text){
+		return new JMenu(text){
+			@Override
+			public void menuSelectionChanged(boolean isIncluded){
+				  super.menuSelectionChanged(isIncluded);
+				  if(isIncluded && !wasMouse){
+					  String menuType = " menu";
+					  if(getMenuBar().getComponentIndex(this) == -1){
+						  menuType = " sub menu";
+					  };
+					  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName()+menuType);
+				  }
+				  wasMouse = false;
+			}
+			
+			@Override
+			public void processMouseEvent(MouseEvent e){
+				wasMouse = true;
+				super.processMouseEvent(e);
+			}
+			
+			private boolean wasMouse = false;
+		};
+	}
+	
+	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;
+		};
+	}
+	
+	/* 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);
+		}
+	};
+	
+	private static final String ACCELERATOR = "accelerator";
+	
+	/*
+	 * this class implements a menu item which speaks out its label when 
+	 * selected with the keyboard (mouse hover will have no effect)
+	 * it needs a reference to the menu bar it's in to implement a respond, with the error sound, to   
+	 * the accelerator even when it's disabled (normally no listeners are called otherwise) 
+	 * 
+	 */
+	private static class SpeechMenuItem extends JMenuItem{
+		@Override
+		public void	processMouseEvent(MouseEvent e ){
+			wasMouse = true;
+			super.processMouseEvent(e);
+		}
+		
+		public SpeechMenuItem(String text){
+			super(text);
+			/* bind ACCELERATOR with the error action */
+			getMenuBar().getActionMap().put(ACCELERATOR, errorAction);
+			wasMouse = false;
+			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");
+			}
+		}
+		
+		@Override
+		public void menuSelectionChanged(boolean isIncluded){
+			  super.menuSelectionChanged(isIncluded);
+			  if(isIncluded && !wasMouse){
+				  String disabled = isEnabled() ? "" : ", disabled";
+				  String accelerator = "";
+				  if(getAccelerator() != null){
+					  accelerator = ", control " + getAccelerator().toString().substring(getAccelerator().toString().lastIndexOf(' '));
+				  }
+				  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName()+disabled+accelerator);
+			  }
+			  wasMouse = false;
+		}
+		
+		@Override
+		public void setEnabled(boolean b){
+			super.setEnabled(b);
+			if(b == false){
+				/* if the menu item gets disabled, then set up an action in the menuBar to respond to 
+				 * the accelerator key stroke, so that the user gets a feedback (error sound) 
+				 * even though the listeners don't get called (as the menu item is disabled) 
+				 */
+				getMenuBar().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(getAccelerator(), ACCELERATOR);
+			}else{
+				getMenuBar().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(getAccelerator(), "none");
+			}
+		}
+		
+		private boolean wasMouse;
+	};
+	
+	private static JMenuBar menuBar;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechOptionPane.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,380 @@
+/*  
+ 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.Color;
+import java.awt.Component;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.text.MessageFormat;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFormattedTextField;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SwingWorker;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+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.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. 
+ *  
+ */
+public abstract class SpeechOptionPane {
+	
+	public static String showTextAreaDialog(Component parentComponent, String message, String initialSelectionValue){
+		JTextArea textArea = new JTextArea(NOTES_TEXT_AREA_ROW_SIZE,NOTES_TEXT_AREA_COL_SIZE);
+		textArea.setText(initialSelectionValue);
+		NarratorFactory.getInstance().speak(message);
+		return textComponentDialog(parentComponent, message, textArea);
+	}
+	
+	public static String showInputDialog(Component parentComponent, String message, String initialSelectionValue){
+		final JTextField textField = new JTextField(initialSelectionValue);
+		textField.selectAll();
+		NarratorFactory.getInstance().speak(message);
+		return textComponentDialog(parentComponent, message, textField);
+	}
+		
+	private static String textComponentDialog(Component parentComponent, String message, final JTextComponent textComponent){	
+		Object componentToDisplay = textComponent;
+		if(textComponent instanceof JTextArea)
+			componentToDisplay = new JScrollPane(textComponent);
+		
+		Object[] displayObjects = { new JLabel(message), componentToDisplay };
+		final JOptionPane optPane = new JOptionPane();
+		optPane.setMessage(displayObjects);
+		optPane.setMessageType(QUESTION_MESSAGE);	
+		optPane.setOptionType(OK_CANCEL_OPTION);
+		/* 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(parentComponent, resources.getString("dialog.speech_option_pane.input"));
+		if(textComponent instanceof JTextArea)
+			dialog.setResizable(true);
+		dialog.addWindowFocusListener(new WindowAdapter(){
+			@Override
+			public void windowGainedFocus(WindowEvent e) {
+				textComponent.requestFocusInWindow();
+		    }
+		});
+		
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		textComponent.addKeyListener(SpeechUtilities.getSpeechKeyListener(true));
+		textComponent.setEditable(true);
+		// start the editing sound
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		
+		if(optPane.getValue() == null)//window closed
+			return null;
+		else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel
+			return null;
+		else{ // pressed on OK
+			return textComponent.getText().trim();
+		}
+	}	
+	
+	public static String showInputDialog(Component parentComponent, String message){
+		return showInputDialog(parentComponent, message, "");
+	}
+	
+	public static Object showSelectionDialog(Component parentComponent, String message, Object[] options, Object initialValue){
+		final LoopComboBox comboBox = new LoopComboBox(options);
+		comboBox.setSelectedItem(initialValue);
+		Object[] displayObjects = { new JLabel(message), comboBox };
+		JOptionPane optPane = new JOptionPane();
+		optPane.setMessage(displayObjects);
+		optPane.setMessageType(QUESTION_MESSAGE);
+		optPane.setOptionType(OK_CANCEL_OPTION);
+		/* 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());
+		
+		NarratorFactory.getInstance().speak(message+", "+SpeechUtilities.getComponentSpeech(comboBox));
+		final JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.select"));
+		dialog.addWindowFocusListener(new WindowAdapter(){
+			@Override
+			public void windowGainedFocus(WindowEvent e) {
+		        comboBox.requestFocusInWindow();
+		    }
+		});
+		
+		comboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
+
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		// start the editing sound
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		if(optPane.getValue() == null)//window closed
+			return null;
+		else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel )//pressed on cancel
+			return null;
+		else{ // pressed on OK
+			return comboBox.getSelectedItem();
+		}
+	}
+	
+	public static Integer showNarratorRateDialog(Component parentComponent, String message, int value, int min, int max){	
+		NarratorFactory.getInstance().speak(message);
+		final JSpinner spinner = new JSpinner(new LoopSpinnerNumberModel(value,min,max));
+		JFormattedTextField tf = ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField();
+		tf.setEditable(false);
+		tf.setFocusable(false);
+		tf.setBackground(Color.white);
+		
+		Object[] displayObjects = { new JLabel(message), spinner};
+		final JOptionPane optPane = new JOptionPane();
+		optPane.setMessage(displayObjects);
+		optPane.setMessageType(QUESTION_MESSAGE);	
+		optPane.setOptionType(OK_CANCEL_OPTION);
+		/* 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(parentComponent, resources.getString("dialog.speech_option_pane.input"));
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		
+		dialog.addWindowFocusListener(new WindowAdapter(){
+			@Override
+			public void windowGainedFocus(WindowEvent e) {
+				spinner.requestFocusInWindow();
+		    }
+		});
+		spinner.addChangeListener(new ChangeListener(){
+			@Override
+			public void stateChanged(ChangeEvent evt) {
+				 JSpinner s = (JSpinner)(evt.getSource());
+				 NarratorFactory.getInstance().setRate((Integer)s.getValue());
+				 NarratorFactory.getInstance().speak(s.getValue().toString());
+			}
+		});
+		// start the editing sound
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		
+		/* set the speech rate back to the value passed as argument */
+		NarratorFactory.getInstance().setRate(value);
+		if(optPane.getValue() == null)//window closed
+			return null;
+		else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel
+			return null;
+		else{ // pressed on OK
+			return (Integer)spinner.getValue();
+		}
+	}
+	
+	public static int showConfirmDialog(Component parentComponent, String message, int optionType){
+		NarratorFactory.getInstance().speak(message);
+		JOptionPane optPane = new JOptionPane();
+		optPane.setMessage(message);
+		optPane.setMessageType(QUESTION_MESSAGE);
+		optPane.setOptionType(optionType);
+		/* 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());
+		
+		JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.confirm"));
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		
+		if(optPane.getValue() == null)//window closed
+			return CANCEL_OPTION;
+		else 
+			return ((Integer)optPane.getValue()).intValue();
+	}
+	
+	public static void showMessageDialog(Component parentComponent, String message, int messageType){
+		NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("dialog.speech_option_pane.message"), message));
+		JOptionPane optPane = new JOptionPane();
+		optPane.setMessage(message);
+		optPane.setMessageType(messageType);
+		/* 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());
+		
+		JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.confirm"));
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+	}
+	
+	public static void showMessageDialog(Component parentComponent, String message){
+		showMessageDialog(parentComponent,message,ERROR_MESSAGE);
+	}
+	
+	public static <T,V> int showProgressDialog(Component parentComponent, String message,final ProgressDialogWorker<T,V> worker, int millisToDecideToPopup){
+		JProgressBar progressBar = new JProgressBar();
+		progressBar.setIndeterminate(true);
+		Object displayObjects[] = {message, progressBar};
+		final JOptionPane optPane = new JOptionPane(displayObjects);
+		optPane.setOptionType(DEFAULT_OPTION);
+		optPane.setOptions(new Object[] {PROGRESS_DIALOG_CANCEL_OPTION});
+		/* 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(parentComponent, resources.getString("dialog.speech_option_pane.download"));
+		SpeechUtilities.changeTabListener(optPane,dialog);
+
+		worker.setDialog(dialog);
+		worker.execute();
+		try {
+			Thread.sleep(millisToDecideToPopup);
+		} catch (InterruptedException ie) {
+			throw new RuntimeException(ie); //should never happen
+		}
+		if(worker.isDone())
+			return OK_OPTION;
+		
+		NarratorFactory.getInstance().speak(message);
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		if(	optPane.getValue() == null ||
+			optPane.getValue() == PROGRESS_DIALOG_CANCEL_OPTION || 
+			Integer.valueOf(CLOSED_OPTION).equals(optPane.getValue())){
+				worker.cancel(true);
+				return CANCEL_OPTION;
+		}
+		return OK_OPTION;
+	}
+	
+	public static Set<Integer> showModifiersDialog(Component parentComponent, String message, List<String> modifierTypes, Set<Integer> modifierIndexes){
+		JOptionPane optPane = new JOptionPane();
+		
+		JPanel checkBoxPanel = new JPanel(new GridLayout(0, 1));
+		final JCheckBox[] checkBoxes = new JCheckBox[modifierTypes.size()]; 
+		for(int i=0;i<checkBoxes.length;i++){
+			checkBoxes[i] = new JCheckBox(modifierTypes.get(i));
+			if(modifierIndexes.contains(i))
+				checkBoxes[i].setSelected(true);
+			checkBoxPanel.add(checkBoxes[i]);
+			checkBoxes[i].addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener());
+		}
+		NarratorFactory.getInstance().speak(message+" "+SpeechUtilities.getComponentSpeech(checkBoxes[0]));
+		
+		Object[] displayObjects = {new JLabel(message),checkBoxPanel};
+		optPane.setMessage(displayObjects);
+		optPane.setMessageType(QUESTION_MESSAGE);
+		optPane.setOptionType(OK_CANCEL_OPTION);
+		/* 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());
+		JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.modifiers"));
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		
+		dialog.addWindowFocusListener(new WindowAdapter(){
+			@Override
+			public void windowGainedFocus(WindowEvent e) {
+		        checkBoxes[0].requestFocusInWindow();
+		    }
+		});
+		
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		
+		if(optPane.getValue() == null)//window closed
+			return null;
+		else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel
+			return null;
+		else{ // pressed on OK
+			Set<Integer> returnSet = new LinkedHashSet<Integer>();
+			for(int i=0;i<checkBoxes.length;i++)
+				if(checkBoxes[i].isSelected()) 
+					returnSet.add(i);
+			return returnSet;
+		}
+	}
+	
+	public static Frame getFrameForComponent(Component parentComponent){
+		return JOptionPane.getFrameForComponent(parentComponent);
+	}
+	
+	private static ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+	private static final int NOTES_TEXT_AREA_COL_SIZE = 10;
+	private static final int NOTES_TEXT_AREA_ROW_SIZE = 10;
+	private static final String PROGRESS_DIALOG_CANCEL_OPTION = resources.getString("dialog.speech_option_pane.cancel"); 
+	
+	
+	public static final int QUESTION_MESSAGE = JOptionPane.QUESTION_MESSAGE;
+	public static final int ERROR_MESSAGE = JOptionPane.ERROR_MESSAGE;
+	public static final int INFORMATION_MESSAGE = JOptionPane.INFORMATION_MESSAGE;
+	public static final int WARNING_MESSAGE = JOptionPane.WARNING_MESSAGE;
+	public static final int OK_CANCEL_OPTION = JOptionPane.OK_CANCEL_OPTION;
+	public static final int CANCEL_OPTION = JOptionPane.CANCEL_OPTION;
+	public static final int OK_OPTION = JOptionPane.OK_OPTION; 
+	public static final int CLOSED_OPTION = JOptionPane.CLOSED_OPTION;
+	public static final int DEFAULT_OPTION = JOptionPane.DEFAULT_OPTION;
+	public static final int YES_NO_OPTION = JOptionPane.YES_NO_OPTION;
+	public static final int YES_OPTION = JOptionPane.YES_OPTION;
+	public static final int NO_OPTION = JOptionPane.NO_OPTION;
+	
+	
+	public static abstract class ProgressDialogWorker<T,V> extends SwingWorker<T,V> {
+		private void setDialog(JDialog dialog){
+			this.dialog = dialog;
+		}
+		
+		@Override
+	    protected void done() {
+			if(dialog != null)
+				dialog.dispose();
+		}
+		
+		private JDialog dialog;
+	}
+	
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechSummaryPane.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,116 @@
+/*  
+ 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.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.KeyStroke;
+
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
+
+/**
+ * Abstract class with an one-line call to display a summary dialog.
+ * The summary text as well as focused components are spoken out through text to speech 
+ * synthesis performed by the {@link Narrator} instance.  
+ * A summary dialog has non editable text field and a button
+ * for confirmation only.  
+ * 
+ *
+ */
+public abstract class SpeechSummaryPane {
+	
+	public static int showDialog(Component parentComponent, String title, String text, int optionType, String[] options){
+		if(optionType == OK_CANCEL_OPTION && options.length < 2)
+			throw new IllegalArgumentException("option type and opions number must be consistent");
+		final JTextArea textArea =  new JTextArea();
+		textArea.setText(text);
+		NarratorFactory.getInstance().speak(title+". "+ text);
+		
+		JScrollPane componentToDisplay = new JScrollPane(textArea);
+		/* set the maximum size: if there is a lot of content yet it doesn't take the whole screen */
+		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+		
+		int editorWidth = (int)screenSize.getWidth() * 5 / 8;
+		int editorHeight = (int)screenSize.getHeight() * 5 / 8;
+		
+		Dimension currentSize = componentToDisplay.getPreferredSize();
+		componentToDisplay.setPreferredSize(new Dimension(
+				Math.min(currentSize.width, editorWidth) , Math.min(currentSize.height, editorHeight)));
+		
+		Object[] displayObjects = { new JLabel(title), componentToDisplay };
+		final JOptionPane optPane = new JOptionPane();
+		optPane.setMessage(displayObjects);
+		optPane.setMessageType(JOptionPane.PLAIN_MESSAGE);	
+		optPane.setOptionType(optionType);
+		/* set the options according to the option type */
+		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(parentComponent,  "");
+		dialog.setResizable(true);
+		
+		dialog.addWindowFocusListener(new WindowAdapter(){
+			@Override
+			public void windowGainedFocus(WindowEvent e) {
+				textArea.requestFocusInWindow();
+		    }
+		});
+		
+		SpeechUtilities.changeTabListener(optPane,dialog);
+		/* the textArea is not editable, so tab key event must not be consumed so that it can be picked up by the focus manager */
+		textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,0), "none");	
+		textArea.addKeyListener(SpeechUtilities.getSpeechKeyListener(false));
+		textArea.setEditable(false);
+		// start the editing sound
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		NarratorFactory.getInstance().shutUp();
+		
+		if(optPane.getValue() == null)//window closed
+			return CANCEL;
+		else if(optPane.getValue().equals(options[OK]))// pressed on OK
+			return OK;
+		else //pressed on cancel
+			return CANCEL;
+	}	
+
+	public static final int OK = 0;
+	public static final int CANCEL = 1;
+	public static final int OK_CANCEL_OPTION = JOptionPane.OK_CANCEL_OPTION;
+	public static final int OK_OPTION = JOptionPane.OK_OPTION;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/TemplateEditor.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,38 @@
+/*  
+ 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.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. 
+ */
+public interface TemplateEditor {
+	public Diagram createNew(Frame frame, Collection<String> existingTemplates);
+	
+	public Diagram edit(Frame frame, Collection<String> existingTemplates, Diagram diagram);
+	
+	public String getLabelForNew();
+	
+	public String getLabelForEdit();
+}
Binary file java/src/uk/ac/qmul/eecs/ccmi/gui/ccmi_favicon.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileChooser.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,55 @@
+/*  
+ 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.filechooser;
+
+import java.awt.Component;
+import java.io.File;
+
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A Component implementing this interface provides the basic file chooser functionality needed to 
+ * in the CCmI Editor.    
+ *
+ *
+ * @see JFileChooser
+ */
+public interface FileChooser {
+	public void setSelectedFile(File file);
+	
+	public File getSelectedFile();
+	
+	public void setFileFilter(FileFilter filter);
+	
+	public void resetChoosableFileFilters();
+	
+	public void setCurrentDirectory(File dir);
+	
+	public File getCurrentDirectory();
+	
+	public int showOpenDialog(Component parent);
+	
+	public int showSaveDialog(Component parent);
+	
+	int APPROVE_OPTION = JFileChooser.APPROVE_OPTION;
+	
+	int CANCEL_OPTION = JFileChooser.CANCEL_OPTION;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileChooserFactory.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,44 @@
+/*  
+ 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.filechooser;
+
+import javax.swing.JFileChooser;
+
+/**
+ *
+ * A Factory class which creates instances of the {@link FileChooser} interface.
+ *
+ */
+public abstract class FileChooserFactory {
+	public static FileChooser getFileChooser(boolean accessible){
+		if(accessible)
+			return new SpeechFileChooser();
+		else 
+			return new NormalFileChooser();
+	}
+	
+	/**
+	 * Adapter class to get JFileChooser to implement the FileChooser Interface
+	 *
+	 */
+	@SuppressWarnings("serial")
+	private static class NormalFileChooser extends JFileChooser implements FileChooser{
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTree.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,286 @@
+/*  
+ 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.filechooser;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.util.LinkedList;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.JTree;
+import javax.swing.KeyStroke;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+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;
+
+/*
+ *
+ * A JTree displaying the content for the local file system.
+ * 
+ *
+ */
+@SuppressWarnings("serial")
+class FileSystemTree extends JTree {
+	FileSystemTree(FileFilter filter){
+		super(new DefaultTreeModel(FileSystemTreeNode.getRootNode(filter)));
+		getAccessibleContext().setAccessibleName(ResourceBundle.getBundle(SpeechFileChooser.class.getName()).getString("tree.accessible_name"));
+		getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+		setCellRenderer(new FileSystemTreeCellRendered(getCellRenderer()));
+
+		setSelectionPath(new TreePath(getModel().getRoot()));
+		overwriteTreeKeystrokes();
+		this.addTreeSelectionListener(new TreeSelectionListener(){
+			@Override
+			public void valueChanged(TreeSelectionEvent evt) {
+				if(treeSelectionListenerGateOpen){
+					FileSystemTreeNode treeNode = (FileSystemTreeNode)evt.getPath().getLastPathComponent();
+					NarratorFactory.getInstance().speak(treeNode.spokenText());
+				}
+			}
+		});
+	}
+
+	@Override
+	public void setSelectionPath(TreePath path){
+		super.setSelectionPath(path);
+		scrollPathToVisible(path);
+	}
+
+	public void setSelectionPath(File file){
+		if(file == null)
+			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);
+		File parent = file.getParentFile();
+		while(parent != null){
+			filePath.add(0, parent);
+			parent = parent.getParentFile();
+		}
+		/* make a TreePath out of the file path */
+		FileSystemTreeNode currentNode = (FileSystemTreeNode)getModel().getRoot();
+		TreePath treePath = new TreePath(currentNode);
+		for(File f : filePath){
+			boolean found = false;
+			for(int i=0;i<currentNode.getChildCount();i++){
+				if(currentNode.getChildAt(i).getFile().equals(f)){
+					currentNode = currentNode.getChildAt(i);
+					treePath = treePath.pathByAddingChild(currentNode);
+					found = true;
+					break;
+				}
+			}
+			if(!found)
+				break;
+		}
+		treeSelectionListenerGateOpen = false;
+		setSelectionPath(treePath);
+		treeSelectionListenerGateOpen = true;
+	}
+
+	public void applyFilter(FileFilter filter){
+		FileSystemTreeNode selectedNode = (FileSystemTreeNode)getSelectionPath().getLastPathComponent();
+		File file = selectedNode.getFile();
+		treeSelectionListenerGateOpen = false;
+		((DefaultTreeModel)getModel()).setRoot(FileSystemTreeNode.getRootNode(filter));
+		treeSelectionListenerGateOpen = true;
+		setSelectionPath(file);
+	}
+
+	@Override
+	protected  void	processMouseEvent(MouseEvent e){
+		//do nothing as the tree does not have to be editable with mouse
+	}
+
+	private void overwriteTreeKeystrokes() {
+		/* overwrite the keys. up and down arrow are overwritten so that it loops when the top and the  */
+		/* bottom are reached rather than getting stuck                                                 */ 
+
+		/* Overwrite keystrokes up,down,left,right arrows and space, shift, ctrl */
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,0),"down");
+		getActionMap().put("down", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				FileSystemTreeNode treeNode = (FileSystemTreeNode)getLastSelectedPathComponent();
+				/* look if we've got a sibling node after (we are not at the bottom) */
+				FileSystemTreeNode nextTreeNode = treeNode.getNextSibling(); 
+				SoundEvent loop = null;
+				if(nextTreeNode == null){
+					TreeNode parent = treeNode.getParent();
+					if(parent == null) /* root node, just stay there */
+						nextTreeNode = treeNode;
+					else /* loop = go to first child of own parent */
+						nextTreeNode = (FileSystemTreeNode)parent.getChildAt(0);
+					loop = SoundEvent.LIST_BOTTOM_REACHED;
+				}
+
+				final String speech = nextTreeNode.spokenText(); 
+				treeSelectionListenerGateOpen = false;
+				setSelectionPath(new TreePath(nextTreeNode.getPath()));
+				treeSelectionListenerGateOpen = true;
+				SoundFactory.getInstance().play(loop, new PlayerListener(){
+					public void playEnded() {
+						NarratorFactory.getInstance().speak(speech);
+					}
+				});
+			}});
+
+		/* Overwrite keystrokes up,down,left,right arrows and space, shift, ctrl */
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP,0),"up");
+		getActionMap().put("up", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				FileSystemTreeNode treeNode = (FileSystemTreeNode)getLastSelectedPathComponent();
+				/* look if we've got a sibling node after (we are not at the bottom) */
+				FileSystemTreeNode peviousTreeNode = treeNode.getPreviousSibling(); 
+				SoundEvent loop = null;
+				if(peviousTreeNode == null){
+					TreeNode parent = treeNode.getParent();
+					if(parent == null) /* root node, just stay there */
+						peviousTreeNode = treeNode;
+					else /* loop = go to first child of own parent */
+						peviousTreeNode = (FileSystemTreeNode)parent.getChildAt(parent.getChildCount()-1);
+					loop = SoundEvent.LIST_TOP_REACHED;
+				}
+
+				final String speech = peviousTreeNode.spokenText();
+				treeSelectionListenerGateOpen = false;
+				setSelectionPath(new TreePath(peviousTreeNode.getPath()));
+				treeSelectionListenerGateOpen = true;
+				SoundFactory.getInstance().play(loop, new PlayerListener(){
+					public void playEnded() {
+						NarratorFactory.getInstance().speak(speech);
+					}
+				});
+			}});
+
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0),"left");
+		getActionMap().put("left", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				TreePath path = getSelectionPath();
+				TreeNode treeNode = (TreeNode)path.getLastPathComponent();
+				final FileSystemTreeNode parent = (FileSystemTreeNode)treeNode.getParent();
+				if(parent == null){/* root node */
+					SoundFactory.getInstance().play(SoundEvent.ERROR);
+				}
+				else{
+					TreePath newPath = new TreePath(parent.getPath());
+					treeSelectionListenerGateOpen = false;
+					setSelectionPath(newPath);
+					collapsePath(newPath);
+					treeSelectionListenerGateOpen = true;
+					SoundFactory.getInstance().play(SoundEvent.TREE_NODE_COLLAPSE,new PlayerListener(){
+						@Override
+						public void playEnded() {
+							NarratorFactory.getInstance().speak(parent.spokenText());
+						}
+					});
+				}
+			}
+		});
+
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0),"right");
+		getActionMap().put("right", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				TreePath path = getSelectionPath();
+				TreeNode treeNode = (TreeNode)path.getLastPathComponent();
+				if(treeNode.isLeaf()){/* leaf node */
+					SoundFactory.getInstance().play(SoundEvent.ERROR);
+				}
+				else{
+					expandPath(path);
+					final FileSystemTreeNode firstChild = (FileSystemTreeNode)treeNode.getChildAt(0); 
+					treeSelectionListenerGateOpen = false;
+					setSelectionPath(new TreePath(firstChild.getPath()));
+					treeSelectionListenerGateOpen = true;
+					SoundFactory.getInstance().play(SoundEvent.TREE_NODE_EXPAND,new PlayerListener(){
+						@Override
+						public void playEnded() {
+							NarratorFactory.getInstance().speak(firstChild.spokenText());
+						}
+					});
+				}
+			}
+		});
+
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0),"space");
+		getActionMap().put("space",new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				TreePath path = getSelectionPath();
+				FileSystemTreeNode treeNode = (FileSystemTreeNode)path.getLastPathComponent();
+				NarratorFactory.getInstance().speak(treeNode.toString());
+			}
+		});
+		
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,InputEvent.CTRL_DOWN_MASK), "ctrl_space");
+		getActionMap().put("ctrl_space", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				TreePath path = getSelectionPath();
+				FileSystemTreeNode treeNode = (FileSystemTreeNode)path.getLastPathComponent();
+				NarratorFactory.getInstance().speak(treeNode.getFile().getPath());
+			}
+		});
+		/* make the tree ignore the page up and page down keys */
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0),"none");
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0),"none");
+	}
+	private boolean treeSelectionListenerGateOpen;
+}
+
+/**
+ * This class  overwrites the default cell renderer in order to always render directories with a  
+ * directory-icon regardless whether they are a leaf node or not. 
+ *
+ */
+class FileSystemTreeCellRendered implements TreeCellRenderer {
+	FileSystemTreeCellRendered(TreeCellRenderer delegate){
+		this.delegate = delegate;
+	}
+
+	@Override
+	public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, 
+			boolean expanded, boolean leaf, int row, boolean hasFocus)  {
+		if(leaf && ((FileSystemTreeNode)value).getFile().isDirectory() )
+			return delegate.getTreeCellRendererComponent(tree, value, selected, expanded, false, row, hasFocus);
+		return delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+	}
+
+	TreeCellRenderer delegate;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTreeCellRenderer.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,45 @@
+/*  
+ 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.filechooser;
+
+import java.awt.Component;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+/*
+ * The cell renderer used in the FileSystemTree class. It differs from the default renderer in 
+ * that it renders leaf nodes as non-leaf nodes they represent an empty directory 
+ * rather than a file.    
+ *
+ */
+@SuppressWarnings("serial")
+class FileSystemTreeCellRenderer extends DefaultTreeCellRenderer {
+	@Override
+	public Component getTreeCellRendererComponent(JTree tree,
+            Object value,
+            boolean selected,
+            boolean expanded,
+            boolean leaf,
+            int row,
+            boolean hasFocus){
+		return super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/FileSystemTreeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,223 @@
+/*  
+ 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.filechooser;
+
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ResourceBundle;
+
+import javax.swing.filechooser.FileFilter;
+import javax.swing.filechooser.FileSystemView;
+import javax.swing.tree.TreeNode;
+
+/*
+ * The tree nodes of a FileSystemTree. Each nodes represent either a directory or a file 
+ * in the local file system where the CCmI Editor is executed. 
+ *
+ */
+class FileSystemTreeNode implements TreeNode {
+	
+	public static FileSystemTreeNode getRootNode(FileFilter filter){
+		return new FileSystemTreeNode(filter){
+			@Override
+			public boolean getAllowsChildren(){
+				return true;
+			}
+			@Override
+			public String toString(){
+				return ResourceBundle.getBundle(SpeechFileChooser.class.getName()).getString("tree_root.label");
+			}
+		};
+	}
+	
+	private FileSystemTreeNode(FileFilter filter){ // constructor for root
+		fileSystemView = FileSystemView.getFileSystemView();
+		parent = null;
+		this.filter = filter;
+		containedFiles = File.listRoots();
+		
+		children = new FileSystemTreeNode[containedFiles.length];
+		for(int i=0; i< children.length; i++){
+			children[i] = new FileSystemTreeNode(this, containedFiles[i],filter);
+			children[i].isFileSystemRoot = true;
+		}
+	}
+	
+	private FileSystemTreeNode(TreeNode parent, File file, FileFilter filter){
+		fileSystemView = FileSystemView.getFileSystemView();
+		this.file = file;
+		this.parent = parent;
+		this.filter = filter;
+		
+		if(file.isDirectory()){
+			containedFiles = file.listFiles();
+			if(containedFiles == null ) 
+				containedFiles = new File[0];
+			ArrayList<File> fileList = new ArrayList<File>(containedFiles.length);
+			for(File f : containedFiles){
+				if(f.isDirectory() || filter.accept(f))
+					fileList.add(f);
+			}
+			Collections.sort(fileList,new FileSystemComparator());
+			containedFiles = new File[fileList.size()];
+			containedFiles = fileList.toArray(containedFiles);
+		}else{
+			containedFiles = new File[0];
+		}
+		children = new FileSystemTreeNode[containedFiles.length];
+	}
+
+	public FileSystemTreeNode getNextSibling(){
+		if(parent == null)
+			return null;
+		int thisIndex = parent.getIndex(this);
+		int numChildren = parent.getChildCount();
+		if(thisIndex == numChildren-1)
+			return null;
+		return (FileSystemTreeNode)parent.getChildAt(thisIndex+1);
+	}
+	
+	public FileSystemTreeNode getPreviousSibling(){
+		if(parent == null)
+			return null;
+		int thisIndex = parent.getIndex(this);
+		if(thisIndex == 0)
+			return null;
+		return (FileSystemTreeNode)parent.getChildAt(thisIndex-1);
+	}
+	
+	public TreeNode[] getPath(){
+		List<TreeNode> pathList = new LinkedList<TreeNode>();
+		pathList.add(0, this);
+		TreeNode parent = getParent();
+		while(parent != null){
+			pathList.add(0,parent);
+			parent = parent.getParent();
+		}
+		TreeNode[] path = new TreeNode[pathList.size()];
+		return pathList.toArray(path);
+	}
+	
+	public File getFile(){
+		return file;
+	}
+	
+	public String spokenText(){
+		String fileType = "dir";
+		if(file != null && file.isFile())
+			fileType = "file";
+		return MessageFormat.format(
+				ResourceBundle.getBundle(SpeechFileChooser.class.getName()).getString(fileType), 
+				toString()
+				); 
+	}
+	
+	@Override
+	public String toString(){
+		if(isFileSystemRoot){
+			String name = fileSystemView.getSystemDisplayName(file);
+			if(name.isEmpty())
+				name = file.toString();
+			return name;
+		}
+		return file.getName();
+	}
+	
+	/* --- TREE NODE INTEFACE IMPLEMENTATION --- */	
+	@Override
+	public Enumeration<FileSystemTreeNode> children() {
+		return new Enumeration<FileSystemTreeNode>(){
+			@Override
+			public boolean hasMoreElements() {
+				return (index < children.length);
+			}
+
+			@Override
+			public FileSystemTreeNode nextElement() {
+				return new FileSystemTreeNode(parent, containedFiles[index++],filter);
+			}
+			int index = 0;
+		};
+	}
+
+	@Override
+	public boolean getAllowsChildren() {
+		return file.isFile();
+	}
+
+	@Override
+	public FileSystemTreeNode getChildAt(int index) throws IndexOutOfBoundsException {
+		/* builds the children lazily */
+		if(children[index] == null)
+			children[index] = new FileSystemTreeNode(this, containedFiles[index],filter);
+		return children[index];
+	}
+
+	@Override
+	public int getChildCount() {
+		return containedFiles.length;
+	}
+
+	@Override
+	public int getIndex(TreeNode n) {
+		FileSystemTreeNode fileSystemTreeNode = (FileSystemTreeNode)n;
+		for(int i=0; i< containedFiles.length; i++){
+			if(fileSystemTreeNode.file.equals(containedFiles[i]))
+				return i;
+		}
+		return -1;
+	}
+
+	@Override
+	public TreeNode getParent() {
+		return parent;
+	}
+
+	@Override
+	public boolean isLeaf() {
+		return (containedFiles.length == 0);
+	}
+	
+	private File[] containedFiles;
+	private File file;
+	private FileSystemTreeNode[] children;
+	private TreeNode parent;
+	private FileSystemView fileSystemView;
+	private FileFilter filter;
+	private boolean isFileSystemRoot;
+}
+
+class FileSystemComparator implements Comparator<File>{
+	@Override
+	public int compare(File f1, File f2) {
+		if(f1.isDirectory() && f2.isFile())
+			return -1;
+		else if(f1.isFile() && f2.isDirectory())
+			return 1;
+		else
+			return f1.compareTo(f2);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,300 @@
+/*  
+ 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.filechooser;
+
+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.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
+import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities;
+
+/*
+ * An accessible file chooser. Users can browse the file system only using hearing as  
+ * in the same fashion as they do when exploring a diagram. 
+ *
+ */
+@SuppressWarnings("serial")
+class SpeechFileChooser extends JPanel implements FileChooser {
+	SpeechFileChooser(){
+		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() {
+		GridBagUtilities gridBag = new GridBagUtilities();
+		JScrollPane scrollPane = new JScrollPane(tree); 
+		add(scrollPane,gridBag.all());
+		add(fileNameLabel,gridBag.label());
+		add(fileNameTextField, gridBag.field());
+		add(fileTypeLabel, gridBag.label());
+		add(fileTypeComboBox, gridBag.field());
+	}
+
+	private void initComponents(){		
+		resources = ResourceBundle.getBundle(this.getClass().getName());
+		/* file name components */
+		fileNameLabel = new JLabel(resources.getString("dialog.file_chooser.file_name"));
+		fileNameTextField = new TreeSelectionTextField();
+		fileNameTextField.setColumns(20);
+		fileNameLabel.setLabelFor(fileNameTextField);
+		fileNameTextField.addKeyListener(SpeechUtilities.getSpeechKeyListener(true));
+		fileTypeLabel = new JLabel(resources.getString("dialog.file_chooser.file_type"));
+
+		/* file type components */
+		Object [] items = {new AllFileFilter(resources.getString("all_file_filter.label"))};
+		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);
+		fileTypeComboBox.addItemListener(new ItemListener(){
+			@Override
+			public void itemStateChanged(ItemEvent evt) {
+				if(evt.getStateChange() == ItemEvent.SELECTED)
+					tree.applyFilter((FileFilter)evt.getItem());
+			}
+		});
+	}
+
+	@Override
+	public void setSelectedFile(File file){
+		selectedFile = file;	
+		if(file == null)
+			return;
+		tree.setSelectionPath(file);
+	}
+
+	@Override
+	public File getSelectedFile(){
+		return selectedFile;
+	}
+
+	@Override
+	public void setFileFilter(FileFilter filter){
+		if(filter != null){
+			FileFilter wrap = new WrapFileFilter(filter);
+			((DefaultComboBoxModel)fileTypeComboBox.getModel()).insertElementAt(wrap, 0);
+			fileTypeComboBox.getModel().setSelectedItem(wrap);
+		}
+	}
+
+	@Override
+	public void resetChoosableFileFilters(){
+		if(fileTypeComboBox.getItemCount() == 2)
+			((DefaultComboBoxModel)fileTypeComboBox.getModel()).removeElementAt(0);
+	}
+
+	@Override 
+	public void setCurrentDirectory(File dir){
+		currentDir = dir;
+		if(dir == null)
+			return;
+		tree.setSelectionPath(dir);
+	}
+
+	@Override 
+	public File getCurrentDirectory(){
+		return currentDir;
+	}
+
+	@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);
+	}
+
+	@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);
+	}
+
+	private int showDialog(Component parent){
+		optPane = new JOptionPane();
+		optPane.setMessage(this);
+		optPane.setMessageType(JOptionPane.PLAIN_MESSAGE);
+		Object[] options = {
+				isOpenFileDialog ? openButton : saveButton,
+				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());
+		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 */
+		fileTypeComboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
+		SoundFactory.getInstance().startLoop(SoundEvent.EDITING);
+		dialog.setVisible(true);
+		SoundFactory.getInstance().stopLoop(SoundEvent.EDITING);
+		fileTypeComboBox.removeItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
+		if((isOpenFileDialog && openButton.equals(optPane.getValue()))||(!isOpenFileDialog && saveButton.equals(optPane.getValue()))){
+			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 */
+			File directory = treeNode.getFile();
+			if(!directory.isDirectory())
+				directory = directory.getParentFile();
+			
+			selectedFile = new File(directory,fileNameTextField.getText());
+			return APPROVE_OPTION;
+		}else{
+			return CANCEL_OPTION;
+		}
+	}
+
+	private ResourceBundle resources;
+
+	private JLabel fileNameLabel;
+	private TreeSelectionTextField fileNameTextField;
+	private JLabel fileTypeLabel;
+	private JComboBox fileTypeComboBox;
+	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 {
+	AllFileFilter(String description){
+		this.description = description;
+	}
+
+	@Override
+	public boolean accept(File file) {
+		return true;
+	}
+
+	@Override
+	public String getDescription() {
+		return description;
+	}
+
+	@Override
+	public String toString(){
+		return description;
+	}
+	String description;
+}
+
+class WrapFileFilter extends FileFilter {
+	WrapFileFilter(FileFilter delegate){
+		this.delegate = delegate;
+	}
+
+	@Override
+	public boolean accept(File f) {
+		return delegate.accept(f);
+	}
+
+	@Override
+	public String getDescription() {
+		return delegate.getDescription();
+	}
+
+	@Override
+	public String toString(){
+		return delegate.getDescription();
+	}
+
+	FileFilter delegate;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,19 @@
+
+dir={0} Directory 
+file={0}
+
+tree_root.label=File System
+tree.accessible_name=File System
+
+open_button.label=Open
+cancel_button.label=Cancel
+save_button.label=Save
+
+dialog.open.title=Open File
+dialog.save.title=Save File
+dialog.open.message=Open File dialog. {0} selected
+dialog.save.message=Save File dialog. {0} selected
+dialog.file_chooser.file_name=File Name: 
+dialog.file_chooser.file_type=File Type: 
+dialog.error.no_file_name=Error: no file name entered
+all_file_filter.label=All Files
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/TreeSelectionTextField.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,44 @@
+/*  
+ 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.filechooser;
+
+import javax.swing.JTextField;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.TreePath;
+
+/*
+ * This text field will display the file name that the user selects when
+ * browsing the tree with the keyboard arrow keys.  
+ */
+@SuppressWarnings("serial")
+class TreeSelectionTextField extends JTextField implements TreeSelectionListener {
+
+	@Override
+	public void valueChanged(TreeSelectionEvent evt) {
+		TreePath path = evt.getPath();
+		FileSystemTreeNode node = (FileSystemTreeNode)path.getLastPathComponent(); 
+		
+		if(node.getFile() != null && node.getFile().isFile())
+			setText(node.toString());
+		else
+			setText("");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/license.txt	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,553 @@
+/*  
+ 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.persistence;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.swing.tree.TreeNode;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+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.TreeModel;
+import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.utils.CharEscaper;
+
+/**
+ * The PersistanceManager provides methods saving and retrieving diagrams from an XML
+ * file. Both templates diagrams (prototypes from which actual diagram instances are created
+ * through cloning) and diagram instances can be saved to a file. 
+ * 
+ */
+public abstract class PersistenceManager {
+	/**
+	 * Encodes a diagram template in a file in XML format
+	 * 
+	 * @param diagram the diagram to be encoded
+	 * @param file the file where the diagram is going to be encoded
+	 * @throws IOException if there are any I/O problems with the file 
+	 */
+	public static void encodeDiagramTemplate(Diagram diagram, File file) throws IOException{
+		ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName());
+		if(file.createNewFile() == false)
+			throw new IOException(resources.getString("dialog.error.file_exists"));
+		
+		DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+        DocumentBuilder docBuilder = null;
+		try {
+			docBuilder = dbfac.newDocumentBuilder();
+		} catch (ParserConfigurationException e) {
+			throw new IOException(resources.getString("dialog.error.problem.save"),e);
+		}
+        Document doc = docBuilder.newDocument();
+        
+        Element root = doc.createElement(DIAGRAM);
+        doc.appendChild(root);
+        /* diagram name and prototypePersstenceDelegate */
+        root.setAttribute(NAME, diagram.getName());
+        root.setAttribute(PROTOTYPE_PERSISTENCE_DELEGATE, diagram.getPrototypePersistenceDelegate().getClass().getName());
+        
+        writePrototypes(doc, root, diagram);
+        
+        //set up a transformer
+        TransformerFactory transfac = TransformerFactory.newInstance();
+        Transformer trans = null;
+		try {
+			trans = transfac.newTransformer();
+		} catch (TransformerConfigurationException tce) {
+			throw new IOException(resources.getString("dialog.error.problem.save"),tce);
+		}
+		trans.setOutputProperty(OutputKeys.INDENT, "yes");
+		trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(2));
+		 
+        StreamResult result = new StreamResult(new BufferedWriter(new FileWriter(file)));
+        DOMSource source = new DOMSource(doc);
+        try {
+			trans.transform(source, result);
+		} catch (TransformerException te) {
+			throw new IOException(resources.getString("dialog.error.problem.save"),te);
+		}
+	}
+	
+	/**
+	 * Decodes a diagram template from a file in XML format
+	 * 
+	 * @param XMLFile the file to read the diagram from 
+	 * @throws IOException if there are any I/O problems with the file 
+	 */
+	public static Diagram decodeDiagramTemplate(File XMLFile) throws IOException{
+		ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName());
+		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+	    DocumentBuilder dBuilder = null;
+		try {
+			dBuilder = dbFactory.newDocumentBuilder();
+		} catch (ParserConfigurationException pce) {
+			throw new IOException(resources.getString("dialog.error.problem.open"),pce);
+		}
+	    Document doc = null;
+		try {
+			doc = dBuilder.parse(XMLFile);
+		} catch (SAXException se) {
+			throw new IOException(resources.getString("dialog.error.problem.open"),se);
+		}
+	    doc.getDocumentElement().normalize();
+	    
+	    if(doc.getElementsByTagName(DIAGRAM).item(0) == null)
+	    	throw new IOException(resources.getString("dialog.error.malformed_file"));
+	    Element root = (Element)doc.getElementsByTagName(DIAGRAM).item(0);
+	    String diagramName = root.getAttribute(NAME);
+	    if(diagramName.isEmpty())
+	    	throw new IOException(resources.getString("dialog.error.malformed_file"));
+	    String persistenceDelegateClassName = root.getAttribute(PROTOTYPE_PERSISTENCE_DELEGATE);
+	    PrototypePersistenceDelegate persistenceDelegate = null;
+	    try{
+	    	Class<? extends PrototypePersistenceDelegate> c = Class.forName(persistenceDelegateClassName).asSubclass(PrototypePersistenceDelegate.class);
+	    	persistenceDelegate = c.newInstance();
+	    }catch(Exception e){
+	    	throw new IOException(resources.getString("dialog.error.problem.open"),e);
+	    }
+	    
+	    final List<Node> nList = readNodePrototypes(doc,persistenceDelegate);
+	    final List<Edge> eList = readEdgePrototypes(doc,persistenceDelegate);
+	    Node[] nArray = new Node[nList.size()];
+	    Edge[] eArray = new Edge[eList.size()];
+	    return  Diagram.newInstance(diagramName,nList.toArray(nArray),eList.toArray(eArray),persistenceDelegate);
+	}
+	
+	/**
+	 * Encodes a diagram instance into the given output stream. Using output stream
+	 * instead of Writer as it's advised by the StreamResult API 
+	 * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html#setOutputStream(java.io.OutputStream)
+	 * 
+	 * @param diagram : the diagram to encode
+	 * @param out : where the diagram will be encoded
+	 * @throws IOException
+	 */
+	public static void encodeDiagramInstance(Diagram diagram, String newName, OutputStream out) throws IOException{
+		ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName());
+		DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+        DocumentBuilder docBuilder = null;
+		try {
+			docBuilder = dbfac.newDocumentBuilder();
+		} catch (ParserConfigurationException pce) {
+			throw new IOException(resources.getString("dialog.error.problem.save"),pce);
+		}
+        Document doc = docBuilder.newDocument();
+        
+        Element root = doc.createElement(DIAGRAM);
+        root.setAttribute(NAME, (newName != null) ? newName : diagram.getName());
+        root.setAttribute(PROTOTYPE_PERSISTENCE_DELEGATE, diagram.getPrototypePersistenceDelegate().getClass().getName());
+        doc.appendChild(root);
+        
+        /* store bookmarks */
+        Element bookmarksTag = doc.createElement(BOOKMARKS);
+        TreeModel<Node,Edge> treeModel = diagram.getTreeModel();
+        for(String key : treeModel.getBookmarks()){
+        	Element bookmarkTag = doc.createElement(BOOKMARK);
+        	bookmarkTag.setAttribute(KEY, key);
+        	if(treeModel.getBookmarkedTreeNode(key).isRoot())
+        		bookmarkTag.setTextContent(ROOT_AS_STRING);
+        	else
+        		bookmarkTag.setTextContent(getTreeNodeAsString(treeModel.getBookmarkedTreeNode(key)));
+        	bookmarksTag.appendChild(bookmarkTag);
+        }
+        if(bookmarksTag.hasChildNodes())
+        	root.appendChild(bookmarksTag);
+        
+        /* store notes */
+        Element notesTag = doc.createElement(NOTES);
+        DiagramModelTreeNode treeRoot = (DiagramModelTreeNode)diagram.getTreeModel().getRoot();
+        for( @SuppressWarnings("unchecked")
+		Enumeration<DiagramModelTreeNode> enumeration = treeRoot.depthFirstEnumeration(); enumeration.hasMoreElements();){
+        	DiagramModelTreeNode treeNode = enumeration.nextElement();
+        	if(!treeNode.getNotes().isEmpty()){
+        		Element noteTag = doc.createElement(NOTE);
+        		Element treeNodeTag = doc.createElement(TREE_NODE);
+        		if(treeNode.isRoot())
+        			treeNodeTag.setTextContent(ROOT_AS_STRING);
+        		else	
+        			treeNodeTag.setTextContent(getTreeNodeAsString(treeNode));
+        		Element contentTag = doc.createElement(CONTENT);
+        		contentTag.setTextContent(CharEscaper.replaceNewline(treeNode.getNotes()));
+        		noteTag.appendChild(treeNodeTag);
+        		noteTag.appendChild(contentTag);
+        		notesTag.appendChild(noteTag);
+        	}
+        }
+        
+        if(notesTag.hasChildNodes())
+        	root.appendChild(notesTag);
+        
+        writePrototypes(doc,root,diagram);
+        
+        Element components = doc.createElement(COMPONENTS);
+        root.appendChild(components);
+        
+        synchronized(diagram.getCollectionModel().getMonitor()){
+	        Collection<Node> nodes = diagram.getCollectionModel().getNodes();
+	        Collection<Edge> edges = diagram.getCollectionModel().getEdges();
+	        
+	        /* store nodes */
+	        Element nodesTag = doc.createElement(NODES);
+	        components.appendChild(nodesTag);
+	        List<Node> nList = new ArrayList<Node>(nodes);        
+	        for(Node n : nList){
+	        	Element nodeTag = doc.createElement(NODE);
+	    		nodeTag.setAttribute(ID, String.valueOf(n.getId()));
+	    		nodeTag.setAttribute(TYPE, n.getType());
+	    		nodesTag.appendChild(nodeTag);
+	        	n.encode(doc, nodeTag);
+	        }
+	        
+	        Element edgesTag = doc.createElement(EDGES);
+	        components.appendChild(edgesTag);
+	        for(Edge e : edges){
+	        	Element edgeTag = doc.createElement(EDGE);
+	        	edgesTag.appendChild(edgeTag);
+	        	e.encode(doc,edgeTag,nList);
+	        }
+        }
+        //set up a transformer
+        TransformerFactory transfac = TransformerFactory.newInstance();
+        Transformer trans = null;
+		try {
+			trans = transfac.newTransformer();
+		} catch (TransformerConfigurationException tec) {
+			throw new IOException(resources.getString("dialog.error.problem.save"));
+		}
+		trans.setOutputProperty(OutputKeys.INDENT, "yes");
+		trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(2));
+		
+        
+        StreamResult result = new StreamResult(out);
+        DOMSource source = new DOMSource(doc);
+        try {
+			trans.transform(source, result);
+		} catch (TransformerException te) {
+			throw new IOException(resources.getString("dialog.error.problem.save"),te);
+		}
+		if(newName != null)
+			diagram.setName(newName);
+	}
+	
+	public static void encodeDiagramInstance(Diagram diagram, OutputStream out) throws IOException{
+		encodeDiagramInstance(diagram,null,out);
+	}
+	
+	public static Diagram decodeDiagramInstance(InputStream in) throws IOException {
+		ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName());
+		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+	    DocumentBuilder dBuilder = null;
+		try {
+			dBuilder = dbFactory.newDocumentBuilder();
+		} catch (ParserConfigurationException pce) {
+			throw new IOException(resources.getString("dialog.error.problem.open"),pce);
+		}
+	    Document doc = null;
+		try {
+			doc = dBuilder.parse(in);
+		} catch (SAXException se) {
+			throw new IOException(resources.getString("dialog.error.problem.open"),se);
+		}
+	    doc.getDocumentElement().normalize();
+	    
+	    if(doc.getElementsByTagName(DIAGRAM).item(0) == null)
+	    	throw new IOException(resources.getString("dialog.error.malformed_file"));
+	    Element root = (Element)doc.getElementsByTagName(DIAGRAM).item(0);
+	    String diagramName = root.getAttribute(NAME);
+	    if(diagramName.isEmpty())
+	    	throw new IOException(resources.getString("dialog.error.malformed_file"));
+	    String persistenceDelegateClassName = root.getAttribute(PROTOTYPE_PERSISTENCE_DELEGATE);
+	    PrototypePersistenceDelegate persistenceDelegate = null;
+	    try{
+	    	Class<? extends PrototypePersistenceDelegate> c = Class.forName(persistenceDelegateClassName).asSubclass(PrototypePersistenceDelegate.class);
+	    	persistenceDelegate = c.newInstance();
+	    }catch(Exception e){
+	    	throw new IOException(resources.getString("dialog.error.problem.open"),e);
+	    }
+	    
+	    final List<Node> nList = readNodePrototypes(doc,persistenceDelegate);
+	    final List<Edge> eList = readEdgePrototypes(doc,persistenceDelegate);
+	    
+	    final Node[] nodes = nList.toArray(new Node[nList.size()]);
+	    final Edge[] edges = eList.toArray(new Edge[eList.size()]);
+	    
+	    Diagram diagram = Diagram.newInstance(diagramName, nodes,edges,persistenceDelegate);
+	    CollectionModel<Node,Edge> collectionModel = diagram.getCollectionModel();
+	    TreeModel<Node,Edge> treeModel = diagram.getTreeModel();
+	    
+	    Map<String,Node> nodesId = new LinkedHashMap<String,Node>();
+	    
+	    if(doc.getElementsByTagName(COMPONENTS).item(0) == null)
+	    	throw new IOException(resources.getString("dialog.error.malformed_file"));
+	    Element componentsTag = (Element)doc.getElementsByTagName(COMPONENTS).item(0);
+	    NodeList componentsChildren = componentsTag.getChildNodes();
+	    Element nodesTag = null;
+	    for(int i=0;i<componentsChildren.getLength();i++){
+	    	if(NODES.equals(componentsChildren.item(i).getNodeName()))
+	    		nodesTag = (Element)componentsChildren.item(i);
+	    }
+	    
+	    NodeList elemList = nodesTag.getElementsByTagName(NODE);
+	    for(int i=0; i<elemList.getLength();i++){
+	    	Element nodeTag = (Element)elemList.item(i);
+	    	String idAsString = nodeTag.getAttribute(ID);
+	    	String type = nodeTag.getAttribute(TYPE);
+	    	Node prototype = null;	    	
+	    	for(Node n : nList)
+	    		if(n.getType().equals(type)){
+	    			prototype = n;
+	    			break;
+	    		}
+	    	if(prototype == null)
+	    		throw new IOException(
+	    				MessageFormat.format(
+	    						resources.getString("dialog.error.node_type_not_present"),
+	    						type));
+	    	Node node = (Node)prototype.clone(); 
+	    	
+	    	nodesId.put(idAsString, node);
+	    	
+			try {
+				Long id = Long.valueOf(idAsString);
+				node.setId(id);
+			}catch(NumberFormatException nfe){
+				throw new IOException(resources.getString("dialog.error.malformed_file"),nfe);
+			}
+			collectionModel.insert(node);
+			try{
+				node.decode(doc, nodeTag);
+			}catch(IOException ioe){ // just give a message to the exception 
+				throw new IOException(resources.getString("dialog.error.malformed_file"),ioe);
+			}
+	    }
+	    
+	    Element edgesTag = null;
+	    for(int i=0;i<componentsChildren.getLength();i++)
+	    	if(EDGES.equals(componentsChildren.item(i).getNodeName()))
+	    		edgesTag = (Element)componentsChildren.item(i);
+
+	    elemList = edgesTag.getElementsByTagName(EDGE);
+	    for(int i=0; i<elemList.getLength();i++){
+	    	Element edgeTag = (Element)elemList.item(i);
+	    	String type = edgeTag.getAttribute(TYPE);
+	    	
+	    	Edge prototype = null;
+	    	for(Edge e : eList)
+	    		if(e.getType().equals(type)){
+	    			prototype = e;
+	    			break;
+	    		}
+	    	if(prototype == null)
+	    		throw new IOException(MessageFormat.format(
+	    				resources.getString("dialog.error.edge_type_not_present"),
+	    				type
+	    				));
+	    	
+	    	Edge edge = (Edge)prototype.clone();
+
+	    	try{
+	    		edge.decode(doc, edgeTag, nodesId);
+	    	}catch(IOException ioe){
+	    		throw new IOException(resources.getString("dialog.error.malformed_file"),ioe);
+	    	}
+	    	collectionModel.insert(edge);
+	    }
+	    
+	    /* retrieve bookmarks */
+	    NodeList bookmarkList = root.getElementsByTagName(BOOKMARK);
+	    for(int i=0;i<bookmarkList.getLength();i++){
+	    	Element bookmarkTag = (Element)bookmarkList.item(i);
+	    	String key  = bookmarkTag.getAttribute(KEY);
+	    	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);
+	    }
+	    
+	    /* retrieve notes */
+	    NodeList noteList = root.getElementsByTagName(NOTE);
+	    for(int i=0;i<noteList.getLength();i++){
+	    	Element noteTag = (Element)noteList.item(i);
+	    	if(noteTag.getElementsByTagName(TREE_NODE).item(0) == null )
+	    		throw new IOException(resources.getString("dialog.error.malformed_file"));
+	    	Element pathTag = (Element)noteTag.getElementsByTagName(TREE_NODE).item(0);
+	    	String path = pathTag.getTextContent();
+	    	if(noteTag.getElementsByTagName(CONTENT).item(0) == null)
+	    		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);
+	    }
+	    
+	    /* normally nodes and edges should be saved in order, this is to prevent *
+	     * a manual editing of the xml to affect  the program logic              */
+	    collectionModel.sort();
+	    /* we have to do this has the insertion in the model made it modified */
+	    collectionModel.setUnmodified();
+    	return diagram;	    
+	}
+	
+	private static void writePrototypes(Document doc, Element root, Diagram diagram){
+		Node[] nodes = diagram.getNodePrototypes();
+		Edge[] edges = diagram.getEdgePrototypes();
+		Element components = doc.createElement(PROTOTYPES);
+        root.appendChild(components);
+        
+        PrototypePersistenceDelegate delegate = diagram.getPrototypePersistenceDelegate();
+        for(Node n : nodes){
+        	Element nodeTag = doc.createElement(NODE);
+    		components.appendChild(nodeTag);
+        	delegate.encodeNodePrototype(doc, nodeTag, n);
+        }
+        
+        for(Edge e : edges){
+        	Element edgeTag = doc.createElement(EDGE);
+    		components.appendChild(edgeTag);
+    		delegate.encodeEdgePrototype(doc, edgeTag, e);
+        }
+	}
+	
+	private static List<Node> readNodePrototypes(Document doc, PrototypePersistenceDelegate delegate) throws IOException{
+		if(doc.getElementsByTagName(PROTOTYPES).item(0) == null)
+			throw new IOException(ResourceBundle.getBundle(PersistenceManager.class.getName()).getString("dialog.error.malformed_file"));
+		Element prototypesTag = (Element)doc.getElementsByTagName(PROTOTYPES).item(0);
+		NodeList elemList = prototypesTag.getElementsByTagName(NODE);
+		final List<Node> nList = new ArrayList<Node>(elemList.getLength());
+	    for(int i=0; i<elemList.getLength();i++){
+	    	Element element = (Element)elemList.item(i);
+	    	try{
+	    		Node n = delegate.decodeNodePrototype(element);
+	    		nList.add(n);
+	    	}catch(IOException ioe){ // just set the message for the exception
+	    		throw new IOException(ResourceBundle.getBundle(PersistenceManager.class.getName()).getString("dialog.error.malformed_file"),ioe);
+	    	}
+	    }
+	    return nList;
+	}
+	
+	private static List<Edge> readEdgePrototypes(Document doc, PrototypePersistenceDelegate delegate) throws IOException{
+		if(doc.getElementsByTagName(PROTOTYPES).item(0) == null)
+			throw new IOException(ResourceBundle.getBundle(PersistenceManager.class.getName()).getString("dialog.error.malformed_file"));
+		Element prototypesTag = (Element)doc.getElementsByTagName(PROTOTYPES).item(0);
+		NodeList elemList = prototypesTag.getElementsByTagName(EDGE);
+	    final List<Edge> eList = new ArrayList<Edge>(elemList.getLength());
+	    for(int i=0; i<elemList.getLength();i++){
+	    	Element element = (Element)elemList.item(i);
+	    	try{
+	    		Edge e = delegate.decodeEdgePrototype(element);
+	    		eList.add(e);
+	    	}catch(IOException ioe){
+	    		throw new IOException(ResourceBundle.getBundle(PersistenceManager.class.getName()).getString("dialog.error.malformed_file"),ioe);
+	    	}
+	    }
+	    return eList;
+	}
+	
+	private static String getTreeNodeAsString(DiagramModelTreeNode treeNode){
+		TreeNode[] path = treeNode.getPath();
+		StringBuilder builder = new StringBuilder();
+		for(int i=0;i<path.length-1; i++)
+			builder.append(String.valueOf(path[i].getIndex(path[i+1]))).append(' ');
+		if(builder.toString().endsWith(" "))
+			builder.deleteCharAt(builder.length()-1);
+		return builder.toString();
+		
+	}
+	
+	private static DiagramModelTreeNode getTreeNodeFromString(TreeModel<Node,Edge> model, String path) throws IOException{
+		DiagramModelTreeNode treeNode = (DiagramModelTreeNode)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));
+		}catch(Exception e){
+			throw new IOException(e);
+		}
+		return treeNode;
+	}
+	
+	public final static String NAME = "Name";
+	public final static String DIAGRAM = "Diagram";
+	public final static String PROTOTYPE_PERSISTENCE_DELEGATE = "PrototypeDelegate";
+	public final static String COMPONENTS = "Components";
+	public final static String PROTOTYPES = "Prototypes";
+	public final static String NODE = "Node";
+	public final static String NODES = "Nodes";
+	public final static String EDGE = "Edge";
+	public final static String EDGES = "Edges";
+	public final static String POSITION = "Position";
+	public final static String PROPERTIES = "Properties";
+	public final static String PROPERTY = "Property";
+	public final static String TYPE = "Type";
+	public final static String VALUE = "Value";
+	public final static String ELEMENT = "Element";
+	public static final String LABEL = "Label";
+	public final static String POINTS = "Points";
+	public final static String POINT = "Point";
+	public final static String ID = "id";
+	public final static String NEIGHBOURS = "Neighbours";
+	public static final String MODIFIER = "Modifier";
+	public static final String MODIFIERS = "Modifiers";
+	public static final String X = "x";
+	public static final String Y = "y";
+	public static final String BOOKMARKS = "Bookmarks";
+	public static final String BOOKMARK = "Bookmark";
+	public static final String KEY = "Key";
+	public static final String NOTES = "Notes";
+	public static final String NOTE = "Note";
+	public static final String CONTENT = "Content";
+	public static final String TREE_NODE = "TreeNode";
+	private static final String ROOT_AS_STRING = "-1"; 
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,8 @@
+
+
+dialog.error.file_exists=File already exist
+dialog.error.node_type_not_present=Node type {0} not present in template definition
+dialog.error.edge_type_not_present=Edge type {0} not present in template definition
+dialog.error.problem.save=Error: a problem occurred while saving the file 
+dialog.error.problem.open=Error: a problem occurred while opening the file
+dialog.error.malformed_file=Error: the opened file is malformed 
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PrototypePersistenceDelegate.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,41 @@
+/*  
+ 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.persistence;
+
+import java.io.IOException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+
+/**
+ *
+ * Each package providing an implementation of nodes and edges must provide a PrototypePersistenceDelegate
+ * as well. This class will be used by the PersistanceManager to save and retrieve the information necessary to rebuild
+ * nodes and edges from an XML file.
+ */
+public interface PrototypePersistenceDelegate {
+	public void encodeNodePrototype(Document doc, Element parent, Node n);
+	public void encodeEdgePrototype(Document doc, Element parent, Edge e);
+	public Node decodeNodePrototype(Element root) throws IOException;
+	public Edge decodeEdgePrototype(Element root) throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/DummyHaptics.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,78 @@
+/*  
+ 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.geom.Line2D;
+import java.util.BitSet;
+
+/*
+ * A dummy implementation of the Haptics interface. All its methods are empty,
+ * so every call will have no effect whatsoever.
+ */
+class DummyHaptics implements Haptics {
+	
+	public DummyHaptics(){}
+
+	@Override
+	public int init(int width, int height) { return 0;}
+
+	@Override
+	public synchronized void addNode(double x, double y, int diagramId) {}
+
+	@Override
+	public synchronized void removeNode(int diagramId) {}
+
+	@Override
+	public synchronized void removeEdge(int diagramId){}
+
+	@Override
+	public synchronized void dispose() {}
+
+	@Override
+	public void addNewDiagram(int id, boolean switchAfter) {}
+
+	@Override
+	public synchronized void switchDiagram(int id) {	}
+
+	@Override
+	public synchronized void removeDiagram(int idToRemove, Integer idNext) {}
+
+	@Override
+	public synchronized void moveNode(double x, double y, int diagramId) {}
+
+	@Override
+	public synchronized void addEdge(int diagramId, double[] xs, double[] ys,
+			BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine) {}
+
+	@Override
+	public synchronized void updateEdge(int diagramId, double[] xs,
+			double[] ys, BitSet[] adjMatrix, int nodeStart, Line2D attractLine) {}
+
+	@Override
+	public synchronized void attractTo(int diagramId) {}
+	
+	@Override
+	public boolean isAlive(){
+		return false;
+	}
+
+	@Override
+	public void run() {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Edge.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,54 @@
+/*  
+ 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.util.BitSet;
+
+/*
+ * A diagram edge in the haptics space. 
+ */
+class Edge {
+	
+	public Edge(int diagramId, int hapticId, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart , int stipplePattern, double attractPointX, double attractPointY ) {
+		assert(xs.length == ys.length);
+		this.size = xs.length;
+		this.diagramId = diagramId;
+		this.hapticId = hapticId;
+		this.xs = xs;
+		this.ys = ys;
+		this.adjMatrix = adjMatrix;
+		this.stipplePattern = stipplePattern;
+		this.attractPointX = attractPointX;
+		this.attractPointY = attractPointY;
+		this.nodeStart = nodeStart;
+	}
+	
+	public double xs[] ;
+	public double ys[] ;
+	public int size;
+	public BitSet adjMatrix[];
+	public int diagramId;
+	public int hapticId;
+	public int stipplePattern;
+	public double attractPointX;
+	public double attractPointY;
+	public int nodeStart;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListener.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,69 @@
+/*  
+ 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;
+
+/**
+ *
+ * An HapticListeners is a thread listening to commands sent by an haptic device through 
+ * shared memory and executes them. The piece of software that manages the haptic device
+ * runs on its own thread, hence the need for a inter-thread communication. Listening 
+ * to the haptic device cannot be done by the event dispatching thread as this 
+ * would prevent the user from using the graphical user interface, therefore a further thread is needed 
+ * for this task.  
+ * HapticListener is an abstract class which must be extended by implementing the 
+ * {@link #executeCommand(HapticListenerCommand, int, double, double, double, double)} method.    
+ *
+ */
+public abstract class HapticListener extends Thread {
+
+	public HapticListener() {
+		super("Haptic Listener");
+		mustSayGoodBye = false;
+	}
+
+	@Override
+	public final void run(){
+		synchronized(this){
+			while(!mustSayGoodBye){
+				try {
+					wait();
+					executeCommand(HapticListenerCommand.fromChar(cmd), diagramElementID, x, y, startX, startY);
+					notify(); // notify the command has been executed 
+				} catch (InterruptedException e) {
+					dispose();
+				}
+			}
+		}
+	}
+	
+	public abstract void executeCommand(HapticListenerCommand cmd, int ID, double x, double y, double startX, double startY);
+	
+	public void dispose(){
+		mustSayGoodBye = true;
+	}
+	
+	private char cmd;
+	private int diagramElementID;
+	private boolean mustSayGoodBye; 
+	private double x;
+	private double y;
+	private double startX;
+	private double startY;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListenerCommand.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,71 @@
+/*  
+ 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;
+
+/**
+ * enum of commands that an haptic listener can receive by an haptic device. 
+ *
+ */
+public enum HapticListenerCommand {
+	PLAY_ELEMENT_SOUND,
+	PLAY_ELEMENT_SPEECH,
+	PLAY_SOUND,
+	SELECT,
+	UNSELECT,
+	MOVE,
+	INFO,
+	NONE,
+	ERROR;
+	
+	public static HapticListenerCommand fromChar(char c){
+		switch(c){
+		case 'p' : return PLAY_ELEMENT_SOUND; 
+		case 't' : return PLAY_ELEMENT_SPEECH;
+		case 's' : return SELECT;
+		case 'm' : return MOVE;
+		case 'i' : return INFO;
+		case 'u' : return UNSELECT;
+		case 'g' : return PLAY_SOUND;
+		case 'e' : return ERROR;
+		default : return NONE;
+		}
+	}
+	
+	public enum Sound { 
+		NONE,
+		MAGNET_OFF,
+		MAGNET_ON,
+		HOOK_ON,
+		DRAG;
+		
+		public static Sound fromInt(int i){
+			switch(i){
+			case 0 : return MAGNET_OFF;
+			case 1 : return MAGNET_ON;
+			case 2 : return HOOK_ON;
+			case 3 : return DRAG;
+			default : return NONE;
+			}
+			
+		}
+	}
+	
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,57 @@
+/*  
+ 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.geom.Line2D;
+import java.io.IOException;
+import java.util.BitSet;
+
+public interface Haptics extends Runnable{
+
+	public int init(int width, int height) throws IOException;
+
+	public void addNewDiagram(int id, boolean switchAfter);
+
+	public void switchDiagram(int id);
+
+	public void removeDiagram(int idToRemove, Integer idNext);
+
+	public void addNode(double x, double y, int diagramId);
+
+	public void removeNode(int diagramId);
+
+	public void moveNode(double x, double y, int diagramId);
+
+	public void addEdge(int diagramId, double[] xs, double[] ys,
+			BitSet[] adjMatrix, int nodeStart, int stipplePattern,
+			Line2D attractLine);
+
+	public void updateEdge(int diagramId, double[] xs, double[] ys,
+			BitSet[] adjMatrix, int nodeStart, Line2D attractLine);
+
+	public void removeEdge(int diagramId);
+
+	public void attractTo(int diagramId);
+	
+	public boolean isAlive();
+
+	public void dispose();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,44 @@
+/*  
+ 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;
+
+/**
+ *
+ * Creates an instance of a class implementing the Haptics interface. There can only be one instance of such 
+ * class. Therefore the factory uses the singleton pattern to always return the same object 
+ * after it is created.
+ *
+ */
+public class HapticsFactory {
+	public static void createInstance(HapticListener listener) {
+		if(hapticsInstance != null)
+			throw new IllegalStateException("create instance must be called once only");
+		hapticsInstance = new DummyHaptics();
+	}
+	
+	public static Haptics getInstance(){
+		if(hapticsInstance == null){ 
+			throw new IllegalStateException("static method createInstance() must be called before getInstance()");
+		}
+		return hapticsInstance;
+	}
+	
+	private static Haptics hapticsInstance;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Node.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,50 @@
+/*  
+ 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.util.ArrayList;
+
+/*
+ * 
+ * A diagram node in the haptics space.
+ * 
+ */
+class Node {
+	public Node(double x, double y, int diagramId, int hapticId){
+		this.x = x;
+		this.y = y;
+		this.diagramId = diagramId;
+		this.hapticId = hapticId;
+		edges = new ArrayList<Edge>(5);
+	}
+	
+	
+	public double x;
+	public double y;
+	/**
+	 * the id on the diagram "id space". it corresponds to the hash code of the 
+	 * diagram nodes
+	 * @see : uk.ac.eecs.qmul.ccmi.components.Node 
+	 */
+	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/main/DiagramEditorApp.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,182 @@
+/*  
+ 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.main;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+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.TemplateEditor;
+import uk.ac.qmul.eecs.ccmi.haptics.Haptics;
+import uk.ac.qmul.eecs.ccmi.haptics.HapticsFactory;
+import uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleTemplateEditor;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.utils.CCmIUncaughtExceptionHandler;
+import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+/**
+ * 
+ * The application class with the main method. The main performs 
+ * the start up initialization  and then displays the graphical user interface.
+ *
+ */
+public class DiagramEditorApp implements Runnable {
+	
+	/**
+	 *  perform initialization prior to displaying the GUI 
+	 */
+	public void init(String[] args) {
+		Thread.setDefaultUncaughtExceptionHandler(new CCmIUncaughtExceptionHandler());
+		final ResourceBundle resources = ResourceBundle.getBundle(this.getClass().getName());
+		/* read command line arguments */
+		if(args.length > 1){
+			System.out.println(resources.getString("usage"));
+			System.exit(-1);
+		}
+		boolean enableLog = false;
+		if(args.length == 1){
+			if(args[0].equals("-l")){
+				enableLog = true;
+				System.out.println("log enabled");
+			}else{
+				System.out.println(resources.getString("usage"));
+				System.exit(-1);
+			}
+		}
+		
+		/* create the home directory if it does not exist and store the path into the preferences */
+		PreferencesService preferences = PreferencesService.getInstance(); 
+		String homeDirPath = preferences.get("home", null);
+		if(homeDirPath == null){
+			homeDirPath = new StringBuilder(System.getProperty("user.home")).append(System.getProperty("file.separator")).append(resources.getString("dir.home")).toString();
+			preferences.put("home", homeDirPath);
+		}
+		File homeDir = new File(homeDirPath);
+		homeDir.mkdir();
+		
+		File backupDir = new File(homeDir,resources.getString("dir.backups"));
+		backupDir.mkdir();
+		backupDirPath = backupDir.getAbsolutePath();
+		
+		/* create the templates directory into the home directory */ 
+		File templateDir = new File(homeDir,resources.getString("dir.templates"));
+		templateDir.mkdir();
+		
+		/* create the images directory into the home directory */
+		File imagesDir = new File(homeDir,resources.getString("dir.images"));
+		if(imagesDir.mkdir())
+			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())
+			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())
+			preferences.put("dir.libs", libsDir.getAbsolutePath());
+		
+		/* 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];
+		}
+
+		if(enableLog){
+			File logDir = new File(homeDir,resources.getString("dir.log"));
+			logDir.mkdir();
+			try{
+				InteractionLog.enable(logDir.getAbsolutePath());
+				InteractionLog.log("PROGRAM STARTED");
+			}catch(IOException ioe){
+				/* if logging was enabled, the possibility to log is considered inescapable */
+				/* do not allow the execution to continue any further                       */
+				throw new RuntimeException(ioe);
+			}
+		}
+		
+		NarratorFactory.createInstance();
+		SoundFactory.createInstance();
+		
+		hapticKindle = new HapticKindle();
+		HapticsFactory.createInstance(hapticKindle);
+		haptics = HapticsFactory.getInstance();
+		if(!haptics.isAlive()){
+			hapticKindle = null;
+		}
+	}
+	
+	/**
+	 * build up the GUI and display it
+	 */
+	@Override
+	public void run() {
+		editorFrame = new EditorFrame(haptics,templateFiles,backupDirPath,getTemplateEditors());
+		if(hapticKindle != null)
+			hapticKindle.setEditorFrame(editorFrame);
+	}
+	
+	public TemplateEditor[] getTemplateEditors(){
+		TemplateEditor[] templateEditors = new TemplateEditor[1];
+		templateEditors[0] = new SimpleTemplateEditor();
+		return templateEditors;
+	}
+
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {
+		DiagramEditorApp application = new DiagramEditorApp();
+		
+		application.init(args);
+		
+		try {
+            SwingUtilities.invokeAndWait(application);
+        } catch (InvocationTargetException ex) {
+            throw new RuntimeException(ex);
+        } catch (InterruptedException ex) {
+        	throw new RuntimeException(ex);
+        }    
+	}
+	
+	EditorFrame editorFrame;
+	HapticKindle hapticKindle;
+	Haptics haptics;
+	File[] templateFiles;
+	TemplateEditor[] templateCreators;
+	String backupDirPath;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,22 @@
+
+template.extension=.xml
+dir.home=ccmi_editor_data
+dir.templates=templates
+dir.diagrams=diagrams
+dir.backups=backups
+dir.images=images
+dir.libs=libs
+dir.log=log
+usage=Unrecognized option(s)\nUsage : java -jar ccmi.jar [-l] \n  -l : enables interaction log
+
+
+#### APPLICATION PREFERENCES ####
+# server.local_port
+# server.remote_port
+# home
+# dir.diagrams
+# dir.images
+# dir.libs
+# laf 
+# recent
+# use_accessible_filechooser
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/CCmIOSCBundle.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,53 @@
+/*  
+ 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 de.sciss.net.OSCBundle;
+
+/**
+ * an OSCBundle implementation for the CCmI Editor. It overrides the behaviour of
+ * getTimeTag() and setTimeTagAbsMillis(long when) in order to provide a 
+ * time tag representation more suitable for the interaction logging. 
+ *
+ */
+class CCmIOSCBundle extends OSCBundle {
+	public CCmIOSCBundle(long timestamp){
+		this.timestamp = timestamp;
+	}
+	
+	public CCmIOSCBundle(){
+		this(System.currentTimeMillis());
+	}
+	
+	@Override
+	public long getTimeTag(){
+		return timestamp;
+	}
+	
+	@Override
+	public void	setTimeTagAbsMillis(long when) {
+		super.setTimeTagAbsMillis(when);
+		this.timestamp = when;
+	}
+	
+	private long timestamp; 
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/CCmIOSCPacketCodec.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,56 @@
+/*  
+ 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 de.sciss.net.OSCPacketCodec;
+
+/*
+ * an OSC OSCPacketCodec with MODE_FAT_V1 support mode. 
+ * 
+ * @see OSCPacketCodec#MODE_FAT_V1
+ */
+class CCmIOSCPacketCodec extends OSCPacketCodec {
+	
+	public CCmIOSCPacketCodec(){
+		super(OSCPacketCodec.MODE_FAT_V1);
+	}
+
+	/*@Override
+	protected  CCmIOSCBundle decodeBundle(ByteBuffer b) throws IOException{
+		OSCBundle oldFashion = super.decodeBundle(b);
+		CCmIOSCBundle newFashion = new CCmIOSCBundle();
+		b.position("#bundle\0".length());
+		long timestamp = b.getLong();
+		newFashion.setTimeTagAbsMillis(timestamp);
+		for(int i = 0; i<oldFashion.getPacketCount();i++){
+			newFashion.addPacket(oldFashion.getPacket(i));
+		}
+		return newFashion;
+	}
+	
+	@Override
+	protected  void	encodeBundle(OSCBundle bndl, ByteBuffer b) throws IOException{
+		super.encodeBundle(bndl, b);
+		super.encodeBundle(bndl, b);
+		b.position("#bundle\0".length());
+		long timestamp = b.getLong();
+		System.out.println("time in encode b" + timestamp);
+	}*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ClientConnectionManager.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,695 @@
+/*  
+ 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 java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+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;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+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.gui.Diagram;
+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.utils.InteractionLog;
+
+/**
+ * This is the class that manages the connection with the server. When a diagram is shared 
+ * this class becomes responsible for actually operating the model (trough the EVT tough, by calling SwingUtilities.invokeLater ).
+ * If the operation is issued by the local user, than it performs the local action with local data only after
+ * being acknowledged by the server, else it creates the data on demand. For example upon an insert 
+ * issued by the server, the element is created from scratch, according to the message of the server.     
+ * 
+ */
+public class ClientConnectionManager extends Thread {
+	 
+	public ClientConnectionManager(EditorTabbedPane tabbedPane) throws IOException{
+		super("Client Connection Manager");
+		channels = new HashMap<SocketChannel, Diagram>();
+		requests = new ConcurrentLinkedQueue<Request>();
+		answers = new LinkedBlockingQueue<Answer>();
+		pendingCommands = new LinkedList<SendCmdRequest>();
+		selector = Selector.open(); 
+		this.tabbedPane = tabbedPane;
+		protocol = ProtocolFactory.newInstance();
+		mustSayGoodbye = false;
+		mustAnswer = false;
+	}
+	
+	/**
+	 * The Event Dispatching Thread communicates with this thread through a concurrent queue. 
+	 * This is the method to add requests to the queue.
+	 * @param r the request for this thread
+	 */
+	public void addRequest(Request r){
+			requests.add(r);
+			selector.wakeup();
+	}
+	
+	public Answer getAnswer(){
+		try {
+			return answers.take();
+		} catch (InterruptedException e) {
+			throw new RuntimeException(e);// must never happen
+		}
+	}
+	
+	@Override
+	public void run(){
+		while(!mustSayGoodbye){
+			try {
+				selector.select();
+			} catch (IOException e) {
+				revertAllDiagrams();
+			}
+			
+			if(mustSayGoodbye)
+				break;
+		
+			/* handle the requests for the server from the local users */
+			handleRequests();
+			
+			for (Iterator<SelectionKey> itr = selector.selectedKeys().iterator(); itr.hasNext();){
+				SelectionKey key = itr.next(); 
+				itr.remove(); 
+				
+				if(!key.isValid())
+					continue;
+				
+				if(key.isReadable()){
+					SocketChannel channel = (SocketChannel)key.channel();
+					Message msg = null;
+					try {
+						msg = protocol.receiveMessage(channel);
+					} catch (IOException e) {
+						revertDiagram(channel);
+						/* signal the event dispatching thread, otherwise blocked */
+						if(mustAnswer){
+							try {
+								answers.put(new ErrorAnswer());
+							} 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; 	
+						/* 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));
+								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));
+								}
+								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));
+								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));
+								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)));
+								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)));
+								break;
+							case SET_PROPERTY : 
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.SetProperty(
+										node,
+										(String)cmd.getArgAt(1),
+										(Integer)cmd.getArgAt(2),
+										(String)cmd.getArgAt(3)
+										));
+								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));
+								SwingUtilities.invokeLater(new CommandExecutor.SetProperties(
+										node,
+										properties
+										));
+								break;		
+							case CLEAR_PROPERTIES :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.ClearProperties(node));
+								break;	
+							case SET_NOTES :
+								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));
+								break;
+							case ADD_PROPERTY :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.AddProperty(
+										node, 
+										(String)cmd.getArgAt(1), 
+										(String)cmd.getArgAt(2)
+										));
+								break;
+							case REMOVE_PROPERTY :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.RemoveProperty(
+										node, 
+										(String)cmd.getArgAt(1),
+										(Integer)cmd.getArgAt(2)));
+								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));
+									}
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.SetModifiers(
+										node,
+										(String)cmd.getArgAt(1),
+										(Integer)cmd.getArgAt(2),
+										indexes
+										));
+								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)));
+								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());
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.SetEndDescription(
+										edge, 
+										node, 
+										(Integer)cmd.getArgAt(2)
+										));
+								break;		
+							case TRANSLATE_NODE :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+								}
+								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)
+										));
+								break;
+							case TRANSLATE_EDGE :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+								}
+								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)
+									));
+								break;
+							case BEND :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+								}
+								Point2D bendStart = null;
+								if(cmd.getArgNum() == 5){
+									bendStart = new Point2D.Double((Double)cmd.getArgAt(3),(Double)cmd.getArgAt(4));
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.Bend(
+										edge,
+										new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
+										bendStart
+									));
+								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));
+								break;
+							case STOP_NODE_MOVE :
+								node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
+								SwingUtilities.invokeLater(new CommandExecutor.StopMove(node));
+								break;
+							}
+					}else if(msg instanceof Reply){
+						Reply reply = (Reply)msg;
+						/* log the reply through the interaction logger, if any */
+						Reply.log(reply);
+						sendCmdRequest = null;
+						for(SendCmdRequest scr : pendingCommands){
+							if(scr.matches(channel, reply.getDiagram())){
+								sendCmdRequest = scr;
+								break;
+							}
+						}
+						assert(sendCmdRequest != null);
+						pendingCommands.remove(sendCmdRequest);
+						switch(reply.getName()){
+							case INSERT_NODE_R :
+							case INSERT_EDGE_R :
+								SwingUtilities.invokeLater(new CommandExecutor.Insert(diagram.getCollectionModel(), sendCmdRequest.element));
+								break;
+							case REMOVE_EDGE_R :
+							case REMOVE_NODE_R :
+								SwingUtilities.invokeLater(new CommandExecutor.Remove(diagram.getCollectionModel(), sendCmdRequest.element));
+								break;
+							case SET_EDGE_NAME_R :
+							case SET_NODE_NAME_R :	
+								SwingUtilities.invokeLater(new CommandExecutor.SetName(sendCmdRequest.element, (String)sendCmdRequest.cmd.getArgAt(1)));
+								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)
+								));
+								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 
+								));
+								break;
+							case CLEAR_PROPERTIES_R :
+								SwingUtilities.invokeLater(new CommandExecutor.ClearProperties((Node)sendCmdRequest.element));
+								break;	
+							case SET_NOTES_R :
+								SwingUtilities.invokeLater(new CommandExecutor.SetNotes(
+										diagram.getTreeModel(),
+										((SendTreeCmdRequest)sendCmdRequest).treeNode,
+										(String)sendCmdRequest.cmd.getArgAt(sendCmdRequest.cmd.getArgNum()-1)
+										));
+								break;
+							case ADD_PROPERTY_R :
+								SwingUtilities.invokeLater(new CommandExecutor.AddProperty(
+									(Node)sendCmdRequest.element,
+									(String)sendCmdRequest.cmd.getArgAt(1),
+									(String)sendCmdRequest.cmd.getArgAt(2)
+									));
+								break;
+							case REMOVE_PROPERTY_R :
+								SwingUtilities.invokeLater(new CommandExecutor.RemoveProperty(
+										(Node)sendCmdRequest.element,
+										(String)sendCmdRequest.cmd.getArgAt(1),
+										(Integer)sendCmdRequest.cmd.getArgAt(2)
+										));
+								break;
+							case SET_MODIFIERS_R :
+								indexes = new HashSet<Integer>(sendCmdRequest.cmd.getArgNum()-3);
+								for(int i=3;i<sendCmdRequest.cmd.getArgNum();i++){
+									indexes.add((Integer)sendCmdRequest.cmd.getArgAt(i));
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.SetModifiers(
+										(Node)sendCmdRequest.element,
+										(String)sendCmdRequest.cmd.getArgAt(1), 
+										(Integer)sendCmdRequest.cmd.getArgAt(2), 
+										indexes
+									));
+								break;
+							case SET_ENDLABEL_R :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									node = Finder.findNode((Long)sendCmdRequest.cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.SetEndLabel(
+										(Edge)sendCmdRequest.element,
+										node, 
+										(String)sendCmdRequest.cmd.getArgAt(2)
+										));
+								break;
+							case SET_ENDDESCRIPTION_R :
+								synchronized(diagram.getCollectionModel().getMonitor()){
+									node = Finder.findNode((Long)sendCmdRequest.cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.SetEndDescription(
+										(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
+									));
+								break;	
+							case TRANSLATE_NODE_R :
+								node = (Node)sendCmdRequest.element;
+								SwingUtilities.invokeLater(new CommandExecutor.Translate(
+										node,
+										new Point2D.Double((Double)sendCmdRequest.cmd.getArgAt(1),(Double)sendCmdRequest.cmd.getArgAt(2)), 
+										(Double)sendCmdRequest.cmd.getArgAt(3), 
+										(Double)sendCmdRequest.cmd.getArgAt(4)
+										));
+								break;
+							case TRANSLATE_EDGE_R :
+								edge = (Edge)sendCmdRequest.element;
+								SwingUtilities.invokeLater(new CommandExecutor.Translate(
+										edge,
+										new Point2D.Double((Double)sendCmdRequest.cmd.getArgAt(1),(Double)sendCmdRequest.cmd.getArgAt(2)), 
+										(Double)sendCmdRequest.cmd.getArgAt(3), 
+										(Double)sendCmdRequest.cmd.getArgAt(4)
+										));
+								break;	
+							case BEND_R :
+								edge = (Edge)sendCmdRequest.element;
+								Point2D bendStart = null;
+								if(sendCmdRequest.cmd.getArgNum() == 5){
+									bendStart = new Point2D.Double((Double)sendCmdRequest.cmd.getArgAt(3),(Double)sendCmdRequest.cmd.getArgAt(4));
+								}
+								SwingUtilities.invokeLater(new CommandExecutor.Bend(
+										edge,
+										new Point2D.Double((Double)sendCmdRequest.cmd.getArgAt(1),(Double)sendCmdRequest.cmd.getArgAt(2)),
+										bendStart
+									));
+								break;
+							case STOP_EDGE_MOVE_R : 
+								edge = (Edge)sendCmdRequest.element;
+								SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge));
+								break;
+							case STOP_NODE_MOVE_R :
+								node = (Node)sendCmdRequest.element;
+								SwingUtilities.invokeLater(new CommandExecutor.StopMove(node));
+								break;
+							case ERROR_R :
+								SwingUtilities.invokeLater(new CommandExecutor.ShowErrorMessageDialog(tabbedPane, "Error for command on "+ sendCmdRequest.element.getName()+ ". " +reply.getMessage()));
+								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 
+						try {
+							answers.put(new LockAnswer((LockMessage)msg));
+							mustAnswer = false;
+						} catch (InterruptedException e) {
+							throw new RuntimeException(e); // must never happen
+						}
+					}
+				}
+			}
+		}
+		/* this part is never reached out unless the thread is shut down */
+		for(SocketChannel channel : channels.keySet()){
+			try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
+		}
+	}
+	
+	public void shutdown(){
+		mustSayGoodbye = true;
+		selector.wakeup();
+	}
+	
+	private void handleRequests() {
+		while(!requests.isEmpty()){
+			Request request = requests.poll();
+			if(request instanceof AddDiagramRequest){
+				AddDiagramRequest adr = (AddDiagramRequest)request;
+				try {
+					adr.channel.configureBlocking(false);
+					adr.channel.register(selector, SelectionKey.OP_READ);
+					channels.put(adr.channel, adr.diagram);
+				} catch (IOException ioe) {
+					/* 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);
+				}
+			}else if(request instanceof RmDiagramRequest){
+				RmDiagramRequest rdr = (RmDiagramRequest)request;
+				Set<Map.Entry<SocketChannel, Diagram>> entryset = channels.entrySet();
+				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();}
+					}
+				}
+			}else if(request instanceof SendCmdRequest||request instanceof SendTreeCmdRequest){
+				SendCmdRequest scr = (SendCmdRequest)request;
+				//System.out.println("ClientConnectionManager:handling request "+scr.cmd.getName());
+				if(!channels.containsKey(scr.channel))
+					continue; // commands issued after reverting a diagram are dropped 
+				pendingCommands.add(scr);
+				try{
+					protocol.send(scr.channel, scr.cmd);
+				}catch(IOException e){
+					/* the pending commands is normally removed upon reply receive */
+					pendingCommands.remove(scr);
+					revertDiagram(scr.channel);
+				}
+			}else if(request instanceof SendLockRequest){
+				SendLockRequest slr = (SendLockRequest)request;
+				try {
+					protocol.send(slr.channel,slr.lock);
+					mustAnswer = true;
+				} catch (IOException e) {
+					revertDiagram(slr.channel);
+					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
+					}
+				}
+			}
+		}
+	}
+
+	private void revertDiagram(SocketChannel c){
+		/* from now on all the commands using this channel will be dropped */
+		final Diagram diagram = channels.remove(c);
+		if(diagram == null)
+			return;
+		try{c.close();}catch(IOException ioe){ioe.printStackTrace();}
+		SwingUtilities.invokeLater(new Runnable(){
+			@Override
+			public void run() {
+				for(int i=0; i< tabbedPane.getTabCount();i++){
+					DiagramPanel dPanel = (DiagramPanel)tabbedPane.getComponentAt(i); 
+					if(dPanel.getDiagram() instanceof NetDiagram){
+						NetDiagram netDiagram = (NetDiagram)dPanel.getDiagram();
+						if( netDiagram.getDelegate().equals(diagram)){
+							/* set the old (unwrapped) diagram as the current one */
+							dPanel.setDiagram(diagram);
+							break;
+						}
+					}
+				}
+				SpeechOptionPane.showMessageDialog(tabbedPane, MessageFormat.format(
+						ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.connection"),
+						diagram.getName())
+				);
+			}
+		});
+		
+	}
+	
+	private void revertAllDiagrams(){
+		for(SocketChannel c : channels.keySet())
+			try{c.close();}catch(IOException ioe){ioe.printStackTrace();}
+		channels.clear();
+		
+		SwingUtilities.invokeLater(new Runnable(){
+			@Override
+			public void run() {
+				for(int i=0; i< tabbedPane.getTabCount();i++){
+					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());
+					}
+				}
+				SpeechOptionPane.showMessageDialog(
+						tabbedPane,
+						ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.connections"));
+			}
+		});
+	}
+	
+	public interface Request {};
+	
+	public interface DiagramRequest extends Request {};
+	
+	public static class AddDiagramRequest implements DiagramRequest {
+		public AddDiagramRequest(SocketChannel channel, Diagram diagram){
+			this.channel = channel; this.diagram = diagram;
+		}
+		public SocketChannel channel;
+		public Diagram diagram;
+	}
+	
+	public static class RmDiagramRequest implements DiagramRequest {
+		public RmDiagramRequest(String diagramName){
+			this.diagramName = diagramName;
+		}
+		public String diagramName;
+	}
+	
+	public static class SendCmdRequest implements Request {
+		public SendCmdRequest(Command cmd, SocketChannel channel, DiagramElement element ){
+			this.cmd = cmd; this.element = element;this.channel = channel;
+		}
+		
+		public boolean matches(SocketChannel c,String diagramName){
+			return(diagramName.equals(cmd.getDiagram())&&c.socket().getInetAddress().equals(channel.socket().getInetAddress()));
+		}
+		public DiagramElement element;
+		public SocketChannel channel;
+		public Command cmd;
+	}
+	
+	public static class SendTreeCmdRequest extends SendCmdRequest{		
+		public SendTreeCmdRequest( Command cmd,SocketChannel channel,DiagramModelTreeNode treeNode) {
+			super(cmd,channel,null);
+			this.treeNode = treeNode;
+		}
+		public DiagramModelTreeNode treeNode;
+		public SocketChannel channel;
+		public Command cmd;
+	}
+	
+	public static class SendLockRequest implements Request {
+		public SendLockRequest (SocketChannel channel, LockMessage lock){
+			this.channel = channel;
+			this.lock = lock;
+		}
+		public SocketChannel channel;
+		public LockMessage lock;
+	}
+	
+	public interface Answer {};
+	public static class LockAnswer implements Answer {
+		public LockAnswer(LockMessage answer){
+			this.message = answer;
+		}
+		public LockMessage message; 
+	}
+	
+	public static class ErrorAnswer implements Answer{
+		
+	}
+	
+	private Node node;
+	private Edge edge;
+	private DiagramModelTreeNode treeNode;
+	private Set<Integer> indexes;
+	private SendCmdRequest sendCmdRequest;
+	/* for each server hold the diagram it shares with it */
+	private Map<SocketChannel, Diagram> channels;
+	private ConcurrentLinkedQueue<Request> requests;
+	private BlockingQueue<Answer> answers;
+	private LinkedList<SendCmdRequest> pendingCommands;
+	private Selector selector;
+	private EditorTabbedPane tabbedPane;
+	private Protocol protocol;
+	private boolean mustAnswer;
+	private volatile boolean mustSayGoodbye;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Command.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,144 @@
+/*  
+ 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.CharEscaper;
+import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
+
+/**
+ * A command message that triggers an update in the model of the target diagram. Possible updates
+ * are those listed in the {@code MessageName} enum.  
+ * Command messages are issued by both clients and server. Clients commands are sent after a
+ * user action. When the server receives a command it broadcasts it to all the clients but the one
+ * from which the command was from. 
+ *
+ */
+public class Command extends Message {
+	public Command(Name name, String diagram, Object[] args, long timestamp){
+		super(timestamp,diagram);
+		this.name = name;
+		this.args = args;
+	}
+	
+	public Command(Name name, String diagram, Object[] args){
+		super(diagram);
+		this.name = name;
+		this.args = args;
+	}
+	
+	public Command(Name name, String diagram, long timestamp){
+		this(name, diagram);
+	}
+	
+	public Command(Name name, String diagram){
+		this(name, diagram, new Object[]{});
+	}
+	
+	public Name getName() {
+		return name;
+	}
+
+	public Object getArgAt(int index) {
+		return args[index];
+	}
+	
+	public Object[] getArgs(){
+		return args;
+	}
+
+	public int getArgNum(){
+		return args.length;
+	}
+	
+
+	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){
+			StringBuilder builder = new StringBuilder(cmd.getName().toString());
+			builder.append(' ').append(cmd.getDiagram());
+			for(int i=0; i<cmd.getArgNum();i++){
+				builder.append(' ').append(cmd.getArgAt(i));
+			}
+			/* replace newlines for notes so that the log has them in one line only */
+			if(cmd.getName() == Command.Name.SET_NOTES){
+				InteractionLog.log("SERVER", action, CharEscaper.replaceNewline(builder.toString()));
+				return;
+			}
+			InteractionLog.log("SERVER", action, builder.toString());
+		}
+	}
+	
+	public static Name valueOf(String n){
+		Name name = Name.NONE;
+		try {
+			name = Name.valueOf(n);
+		}catch(IllegalArgumentException iae){
+			name.setOrigin(n); 
+		}
+		return name;
+	}
+	
+	private Name name;
+	private Object[] args;
+	
+	public static enum Name implements Message.MessageName {
+		NONE,
+		LIST,
+		GET,
+		LOCAL,
+		INSERT_EDGE,
+		INSERT_NODE,
+		REMOVE_NODE,
+		REMOVE_EDGE,
+		SET_NODE_NAME,
+		SET_EDGE_NAME,
+		SET_PROPERTY,
+		SET_PROPERTIES,
+		CLEAR_PROPERTIES,
+		SET_NOTES,
+		ADD_PROPERTY,
+		REMOVE_PROPERTY,
+		SET_MODIFIERS,
+		SET_ENDDESCRIPTION,
+		SET_ENDLABEL,
+		TRANSLATE_NODE,
+		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;
+	}	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/CommandExecutor.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,301 @@
+/*  
+ 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 java.awt.Component;
+import java.awt.geom.Point2D;
+import java.util.Set;
+
+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.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.GraphElement;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
+/**
+ * This class inner classes are used to invoke commands on the Event Dispatching thread by
+ * the networking threads receiving replies or commands by the server. Its aim
+ * is to preserve reference integrity in the changeover between the two threads
+ * 
+ *
+ */
+public abstract class CommandExecutor implements Runnable {
+
+	public static class Insert extends CommandExecutor{
+		public Insert(CollectionModel<Node,Edge> m, DiagramElement de){
+			element = de;
+			model = m;
+		}
+		@Override
+		public void run() {
+			if(element instanceof Node)
+				model.insert((Node)element);
+			else
+				model.insert((Edge)element);
+		}
+		private CollectionModel<Node,Edge> model;
+		private DiagramElement element;
+	}
+	
+	public static class Remove extends CommandExecutor{
+		public Remove(CollectionModel<Node,Edge> m, DiagramElement de) {
+			model = m;
+			element = de;
+		}
+		@Override
+		public void run() {
+			model.takeOut(element);
+		}
+		private CollectionModel<Node,Edge> model;
+		private DiagramElement element;
+	}
+	
+	public static class SetName extends CommandExecutor {
+		public SetName(DiagramElement de, String n){
+			element = de;
+			name = n;
+		}
+		@Override
+		public void run(){
+			element.setName(name);
+		}
+		private String name;
+		private DiagramElement element;
+	}
+	
+	
+	public static class SetProperty extends CommandExecutor{
+		public SetProperty(Node n, String t, Integer i, String v){
+			node = n;
+			type = t;
+			index = i;
+			value = v;
+		}
+		
+		@Override
+		public void run(){
+			node.setProperty(type, index, value);
+		}
+		
+		private Node node;
+		private String type;
+		private Integer index;
+		private String value;
+	}
+	
+	public static class SetProperties extends CommandExecutor {
+		public SetProperties(Node n, NodeProperties p){
+			node = n;
+			properties = p;
+		}
+		@Override
+		public void run(){
+			node.setProperties(properties);
+		}
+		private Node node;
+		private NodeProperties properties;
+	}
+	
+	public static class ClearProperties extends CommandExecutor {
+		public ClearProperties(Node n){
+			node = n;
+		}
+		@Override
+		public void run(){
+			node.clearProperties();
+		}
+		private Node node;
+	}
+	
+	public static class SetNotes extends CommandExecutor{
+		public SetNotes(TreeModel<Node,Edge>  m, DiagramModelTreeNode tn, String n){
+			model = m;
+			treeNode = tn;
+			notes = n;
+		}
+		
+		@Override
+		public void run(){
+			model.setNotes(treeNode, notes);
+		}
+		private DiagramModelTreeNode treeNode;
+		private String notes;
+		private TreeModel<Node,Edge>  model;
+	}
+	
+	public static class AddProperty extends CommandExecutor {
+		public AddProperty(Node n, String t, String v){
+			node = n;
+			type = t;
+			value = v;
+		}
+		@Override
+		public void run(){
+			node.addProperty(type, value);
+		}
+		private Node node;
+		private String type;
+		private String value;
+	}
+	
+	public static class RemoveProperty extends CommandExecutor {
+		public RemoveProperty(Node n, String t, int i){
+			node = n;
+			type = t;
+			index = i;
+		}
+		
+		@Override
+		public void run(){
+			node.removeProperty(type, index);
+		}
+		
+		private Node node;
+		private String type;
+		private int index;
+	}
+	
+	public static class SetModifiers extends CommandExecutor {
+		public SetModifiers(Node n,String t,Integer v, Set<Integer> i){
+			node = n;
+			type = t;
+			value = v;
+			indexes = i;
+		}
+		@Override
+		public void run(){
+			node.setModifierIndexes(type, value, indexes);
+		}
+		private Node node;
+		private String type;
+		private Integer value;
+		private Set<Integer> indexes;
+	}
+	
+	public static class SetEndLabel extends CommandExecutor {
+		public SetEndLabel(Edge e, Node n, String l){
+			edge = e;
+			node = n;
+			label = l;
+		}
+		@Override
+		public void run(){
+			edge.setEndLabel(node, label);
+		}
+		private Node node;
+		private Edge edge;
+		private String label;
+	}
+	
+	public static class SetEndDescription extends CommandExecutor {
+		public SetEndDescription(Edge e, Node n, int i){
+			edge = e;
+			node = n;
+			index = i;
+		}
+		@Override
+		public void run(){
+			edge.setEndDescription(node, index);
+		}
+		private Node node;
+		private Edge edge;
+		private int index;
+	}
+	
+	public static class Translate extends CommandExecutor {
+		public Translate(GraphElement ge, Point2D p, Double x, Double y){
+			element = ge;
+			point = p;
+			dx = x;
+			dy = y;
+		}
+		
+		@Override
+		public void run(){
+			element.translate(point, dx, dy);
+		}
+		
+		private GraphElement element;
+		private Point2D point;
+		private Double dx;
+		private Double dy;
+	}
+	
+	public static class StartMove extends CommandExecutor {
+		public StartMove(GraphElement ge, Point2D p){
+			element = ge;
+			point = p;
+		}
+		@Override
+		public void run(){
+			element.startMove(point);
+		}
+		private GraphElement element;
+		private Point2D point;
+	}
+	
+	public static class Bend extends CommandExecutor {
+		public Bend(Edge e, Point2D p, Point2D bs){
+			edge = e;
+			point = p;
+			bendStart = bs;
+		}
+		@Override
+		public void run(){
+			if(bendStart != null)
+				edge.startMove(bendStart);
+			edge.bend(point);
+		}
+		private Edge edge;
+		private Point2D point;
+		private Point2D bendStart;
+	}
+	
+	public static class StopMove extends CommandExecutor {
+		public StopMove(GraphElement ge){
+			element = ge;
+		}
+		@Override
+		public void run(){
+			element.stopMove();
+		}
+		private GraphElement element;
+	}
+	
+	public static class ShowErrorMessageDialog extends CommandExecutor {
+		public ShowErrorMessageDialog(Component c, String msg){
+			message = msg;
+			parentComponent = c; 
+		}
+		@Override
+		public void run(){
+			SpeechOptionPane.showMessageDialog(parentComponent, message);
+		}
+		Component parentComponent;
+		String message;
+	}
+	
+	@Override
+	public abstract void run();
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/DiagramAlreadySharedException.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,32 @@
+/*  
+ 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;
+
+/**
+ * If the user tries to share the same diagram twice, they will get this exception thrown. 
+ *
+ */
+@SuppressWarnings("serial")
+public class DiagramAlreadySharedException extends DiagramShareException {
+	DiagramAlreadySharedException(String msg){
+		super(msg);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/DiagramDownloader.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,88 @@
+/*  
+ 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 java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+
+import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+/**
+ * A {@code SwingWorker} that takes on the communication with the server in the very first phase of the 
+ * connection for a new diagram. It handles the download of the list of diagrams available for sharing on the server
+ * and, once the user has chosen one, it downloads the diagram into the local editor. 
+ * Since this tasks can take a long time due to network delay and the interaction has not yet started
+ * a {@code SwingWorker} is used so that the user interface won't get stuck in the process, the user
+ * being able to cancel the job and to go back to the diagram editor. 
+ *
+ */
+public class DiagramDownloader extends SpeechOptionPane.ProgressDialogWorker<String,Void> {
+
+	public DiagramDownloader(SocketChannel channel, String target, Task task){
+		this.channel = channel;
+		this.task = task;
+		if(task == Task.CONNECT_AND_DOWNLOAD_LIST)
+			this.address = target;
+		else
+			this.diagramName = target;
+	}
+	
+	@Override
+	protected String doInBackground() throws Exception {
+		if(task == Task.CONNECT_AND_DOWNLOAD_LIST){
+			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,""));
+				break;
+			case DOWNLOAD_DIAGRAM :	
+				protocol.send(channel, new Command(Command.Name.GET ,diagramName));
+		}
+		Reply reply = protocol.receiveReply(channel);
+		switch(reply.getName()){
+		case ERROR_R : 
+			throw new DiagramShareException(reply.getMessage());
+		case LIST_R :
+			String result = new String(reply.getMessage());
+			if("".equals(result))
+				return null;
+			return result;
+		case GET_R : 
+			return reply.getMessage();
+		default : throw new RuntimeException();
+		}
+	}
+	
+	public static enum Task{
+		CONNECT_AND_DOWNLOAD_LIST,
+		DOWNLOAD_DIAGRAM;
+	};
+
+	private SocketChannel channel;
+	private String diagramName;
+	private String address;
+	private Task task;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/DiagramShareException.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,32 @@
+/*  
+ 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;
+
+/**
+ * This exception is thrown when a problem occurred in the process of sharing a diagram 
+ * via the server with other remote users  
+ *
+ */
+@SuppressWarnings("serial")
+public class DiagramShareException extends Exception {
+	public DiagramShareException(String arg0) {
+		super(arg0);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/LockMessage.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,141 @@
+/*  
+ 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;
+
+/**
+ * This class represents a lock message,through which the clients get exclusivity on  
+ * editing the elements of the diagram. The class is used by both the client, 
+ * to request a lock, and the server, to acknowledge the success/failure of the request.
+ * 
+ * The argument of the message can be either the path to a three node or the id of a diagram element.
+ * The former is used for lock for notes editing which can concern every tree node in the tree, 
+ * the latter for all the other types of lock as they only concern diagram elements.   
+ * The path to a tree node is a sequence of integers representing the index of the children 
+ * from the root to the affected node. So for instance 4,2,3 would be the third son of the 
+ * second son of the fourth son of the root node. 
+ */
+public class LockMessage extends Message {
+
+	/**
+	 * 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);
+		this.args = args;
+		this.name = name;
+	}
+
+	/**
+	 * 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);
+		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, String diagram, long id){
+		this(name,diagram,new Object[]{id});
+	}
+	
+	@Override
+	public Name getName() {
+		return name;
+	}
+	
+	public Object getArgAt(int index) {
+		return args[index];
+	}
+	
+	public Object[] getArgs(){
+		return args;
+	}
+
+	public int getArgNum(){
+		return args.length;
+	}
+	
+	public static LockMessage.Name valueOf(String n){
+		Name name = Name.NONE_L;
+		try {
+			name = Name.valueOf(n);
+		}catch(IllegalArgumentException iae){
+			name.setOrigin(n); 
+		}
+		return name;
+	}
+
+	/** used to distinguish between different kinds of messages. */
+	public static final String LOCK_NAME_POSTFIX = "_L";
+	public static final String GET_LOCK_PREFIX = "GET_";
+	public static final String YIELD_LOCK_PREFIX = "YIELD_";
+	private Name name;
+	private Object[] args;
+	
+	/**
+	 * enum containing all the possible lock messages that can be exchanged 
+	 * between server and client in either direction.
+	 */
+	public static enum Name implements Message.MessageName {
+		GET_DELETE_L,
+		GET_NAME_L,
+		GET_PROPERTIES_L,
+		GET_EDGE_END_L,
+		GET_MOVE_L,
+		GET_NOTES_L,
+		GET_BOOKMARK_L,
+		GET_MUST_EXIST_L,
+		
+		YIELD_DELETE_L,
+		YIELD_NAME_L,
+		YIELD_PROPERTIES_L,
+		YIELD_EDGE_END_L,
+		YIELD_MOVE_L,
+		YIELD_NOTES_L,
+		YIELD_BOOKMARK_L,
+		YIELD_MUST_EXISTS_L,
+		
+		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;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/LockMessageConverter.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,103 @@
+/*  
+ 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.Lock;
+
+/**
+ * A utility class providing static methods to convert a Lock into the LockMessage 
+ * conveying it. And the other way around. 
+ * 
+ *
+ */
+public class LockMessageConverter {
+	/**
+	 * Creates a lock message out of a lock passed as argument 
+	 * @param lock the lock to be converted
+	 * @param isGet whether it's a "get-lock" or "yield-lock" message
+ 	 * @return the lock message
+	 */
+	public static LockMessage.Name getLockMessageNamefromLock(Lock lock, boolean isGet){
+		LockMessage.Name name = LockMessage.Name.NONE_L;
+		switch (lock){
+		case DELETE : name = ((isGet) ?  LockMessage.Name.GET_DELETE_L : LockMessage.Name.YIELD_DELETE_L);
+			break;
+		case NAME : name = (isGet) ? LockMessage.Name.GET_NAME_L : LockMessage.Name.YIELD_NAME_L ;
+			break;
+		case PROPERTIES : name = (isGet) ? LockMessage.Name.GET_PROPERTIES_L : LockMessage.Name.YIELD_PROPERTIES_L ;
+			break;
+		case EDGE_END : name = (isGet) ? LockMessage.Name.GET_EDGE_END_L : LockMessage.Name.YIELD_EDGE_END_L ;
+			break;
+		case MOVE : name = (isGet) ? LockMessage.Name.GET_MOVE_L : LockMessage.Name.YIELD_MOVE_L ;
+			break;
+		case NOTES : name = (isGet) ? LockMessage.Name.GET_NOTES_L : LockMessage.Name.YIELD_NOTES_L ;
+			break;
+		case BOOKMARK : name = (isGet) ? LockMessage.Name.GET_BOOKMARK_L : LockMessage.Name.YIELD_BOOKMARK_L ;
+			break;
+		case MUST_EXIST : name = (isGet) ? LockMessage.Name.GET_MUST_EXIST_L : LockMessage.Name.YIELD_MUST_EXISTS_L;
+			break;
+		}
+		return name;
+	}
+	
+	/**
+	 * Returns the lock conveyed by the lock message passed as argument 
+	 * @param name the lock message name @see LockMessage.Name
+	 * @return the conveyed lock 
+	 */
+	public static Lock getLockFromMessageName(LockMessage.Name name){
+		Lock lock = Lock.NONE;
+		switch(name){
+		case GET_DELETE_L :
+		case YIELD_DELETE_L :
+			lock = Lock.DELETE;
+			break;
+		case GET_NAME_L : 
+		case YIELD_NAME_L :
+			lock = Lock.NAME;
+			break;
+		case GET_PROPERTIES_L :
+		case YIELD_PROPERTIES_L :
+			lock = Lock.PROPERTIES;
+			break;
+		case GET_EDGE_END_L :
+		case YIELD_EDGE_END_L :
+			lock = Lock.EDGE_END;
+			break;
+		case GET_MOVE_L :
+		case YIELD_MOVE_L:
+			lock = Lock.MOVE;
+			break;
+		case GET_NOTES_L :
+		case YIELD_NOTES_L :
+			lock = Lock.NOTES;
+			break;
+		case GET_BOOKMARK_L :
+		case YIELD_BOOKMARK_L :
+			lock = Lock.BOOKMARK;
+			break;
+		case GET_MUST_EXIST_L :
+		case YIELD_MUST_EXISTS_L:
+			lock = Lock.MUST_EXIST;
+			break;
+		}
+		return lock;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Message.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,54 @@
+/*  
+ 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;
+
+/**
+ * A basic implementation of a message exchanged between server and client.  
+ *
+ */
+public abstract class Message {
+	
+	public Message(long timestamp, String diagram){
+		this.timestamp = timestamp;
+		this.diagram = diagram;
+	}
+	
+	public Message(String diagram){
+		this(System.currentTimeMillis(),diagram);
+	}
+	
+	public long getTimestamp() {
+		return timestamp;
+	}
+	
+	public String getDiagram(){
+		return diagram;
+	}
+	
+	public abstract MessageName getName();
+	
+	private long timestamp;
+	private String diagram;
+	
+	public static interface MessageName {
+		String toString();
+	}
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/NetDiagram.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,537 @@
+/*  
+ 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 java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.nio.channels.SocketChannel;
+import java.util.Queue;
+import java.util.Set;
+
+import javax.swing.tree.TreeNode;
+
+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.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
+import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+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.persistence.PrototypePersistenceDelegate;
+import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.LockAnswer;
+import uk.ac.qmul.eecs.ccmi.utils.ExceptionHandler;
+
+/**
+ * 
+ * A NetDiagram is a diagram that is shared by connecting to a server on either a remote or local host.
+ * That means that other users from other computers can modify the diagram model through the server. 
+ * A NetDiagram is created by wrapping a local diagram (a diagram open in the local editor) into a NetDiagram class.
+ * The wrapped diagram works as a delegate. What Really changes between a local diagram and a network diagram is 
+ * that the modelUpdater will directly affect the diagram model for the former and exchange messages with the server 
+ * for the latter. In the case of a network diagram the changes to the model are actually made by a {@link ClientConnectionManager}
+ * thread upon receiving a message from the server.  
+ *
+ */
+public abstract class NetDiagram extends Diagram {
+	
+	private NetDiagram(Diagram delegateDiagram){
+		this.delegateDiagram = delegateDiagram;
+	}
+	
+	public static NetDiagram wrapRemoteHost(Diagram diagram, ClientConnectionManager connectionManager,SocketChannel channel){
+		return new RemoteHostDiagram(diagram,connectionManager,channel);
+	}
+	
+	public static NetDiagram wrapLocalHost(Diagram diagram, SocketChannel channel, Queue<DiagramElement> dElements, ExceptionHandler handler){
+		return new LocalHostDiagram(diagram,channel,dElements,handler);
+	}
+	
+	@Override
+	public String getName(){
+		return delegateDiagram.getName();
+	}
+	
+	@Override
+	public String toString(){
+		return getName();
+	}
+	
+	@Override
+	public PrototypePersistenceDelegate getPrototypePersistenceDelegate(){
+		return delegateDiagram.getPrototypePersistenceDelegate();
+	}
+
+	@Override
+	public TreeModel<Node,Edge> getTreeModel(){
+		return delegateDiagram.getTreeModel();
+	}
+
+	@Override
+	public CollectionModel<Node,Edge> getCollectionModel(){
+		return delegateDiagram.getCollectionModel();
+	}
+
+	@Override
+	public void setName(String name) {
+		delegateDiagram.setName(name);	
+	}
+
+	@Override
+	public Node[] getNodePrototypes() {
+		return delegateDiagram.getNodePrototypes();
+	}
+
+	@Override
+	public Edge[] getEdgePrototypes() {
+		return delegateDiagram.getEdgePrototypes();
+	}
+	
+	@Override
+	public DiagramModelUpdater getModelUpdater(){
+		return innerModelUpdater;
+	}
+	
+	public Diagram getDelegate(){
+		return delegateDiagram;
+	}
+	
+	public abstract SocketChannel getSocketChannel();
+	
+	protected abstract void send(Command cmd, DiagramElement element);
+	
+	protected abstract void send(Command cmd, DiagramModelTreeNode treeNode);
+	
+	protected abstract void send(LockMessage lockMessage);
+	
+	protected abstract boolean receiveLockAnswer();
+	
+	private Diagram delegateDiagram;
+	private InnerModelUpdater innerModelUpdater = new InnerModelUpdater(); 
+	
+	private class InnerModelUpdater implements DiagramModelUpdater {
+		
+		@Override
+		public boolean getLock(DiagramModelTreeNode treeNode, Lock lock) {
+			try {
+				sendLockMessage(treeNode,lock,true);
+			}catch(IllegalArgumentException iae){
+				return false;
+			}
+			return receiveLockAnswer();
+		}
+		
+		@Override
+		public void yieldLock(DiagramModelTreeNode treeNode, Lock lock) {
+			try {
+				sendLockMessage(treeNode,lock,false);
+			}catch(IllegalArgumentException iae) {}
+		}
+		
+		private void sendLockMessage(DiagramModelTreeNode treeNode, Lock lock, boolean isGettingLock){
+			TreeNode[] path = treeNode.getPath();
+			Object[] args = new Object[path.length-1];
+			if(args.length == 0 && !treeNode.isRoot())
+				throw new IllegalArgumentException("it's a node no longer connected with the tree");
+			for(int i=0;i<path.length-1;i++){
+				args[i] = delegateDiagram.getTreeModel().getIndexOfChild(path[i], path[i+1]);
+			}
+			send(new LockMessage(
+					LockMessageConverter.getLockMessageNamefromLock(lock,isGettingLock),
+					delegateDiagram.getName(),
+					args
+			));
+		}
+
+		@Override
+		public void insertInCollection(DiagramElement element) {
+			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()}
+				);
+			}else{
+				Edge edge = (Edge)element;
+				Object args[] = new Object[1+edge.getNodesNum()];
+				args[0] = edge.getType();
+				/* the args of the command will be the id's of the connected edges */
+				for(int i = 1; i< args.length; i++)
+					args[i] = edge.getNodeAt(i-1).getId();
+				cmd = new Command(
+						Command.Name.INSERT_EDGE,
+						delegateDiagram.getName(),
+						args
+				);
+			}
+			send(cmd,element);
+		}
+
+		@Override
+		public void insertInTree(DiagramElement element) {
+			insertInCollection(element);
+		}
+
+		@Override
+		public void takeOutFromCollection(DiagramElement element) {
+			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()}
+			);
+			send(cmd,element);
+		}
+
+		@Override
+		public void takeOutFromTree(DiagramElement element) {
+			takeOutFromCollection(element);
+		}
+
+		@Override
+		public void setName(DiagramElement element, String name) {
+			send(new Command(
+					element instanceof Node ? Command.Name.SET_NODE_NAME : Command.Name.SET_EDGE_NAME, 
+					delegateDiagram.getName(),
+					new Object[] {element.getId(), name}),
+				 element);	
+		}
+
+		@Override
+		public void setNotes(DiagramModelTreeNode treeNode, String notes) {
+			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);
+			send(cmd,treeNode);
+		}
+
+		@Override
+		public void setProperty(Node node, String type, int index, String value) {
+			send(new Command(Command.Name.SET_PROPERTY, 
+					delegateDiagram.getName(),
+					new Object[] {node.getId(),type,index,value}), 
+				 node
+			);
+		}
+
+		@Override
+		public void setProperties(Node node, NodeProperties properties) {
+			send(new Command(Command.Name.SET_PROPERTIES, 
+					delegateDiagram.getName(),
+					new Object[] {node.getId(),properties.toString()}),
+				node	
+			);
+			
+		}
+
+		@Override
+		public void clearProperties(Node node) {
+			send(new Command(Command.Name.CLEAR_PROPERTIES,
+					delegateDiagram.getName(),
+					new Object[] {node.getId()}),
+				node	
+			);
+		}
+
+		@Override
+		public void addProperty(Node node, String type, String value) {
+			send(new Command(Command.Name.ADD_PROPERTY,
+					delegateDiagram.getName(),
+					new Object[] {node.getId(),type,value}),
+				node	
+			);
+		}
+
+		@Override
+		public void removeProperty(Node node, String type, int index) {
+			send(new Command(Command.Name.REMOVE_PROPERTY, 
+					delegateDiagram.getName(),
+					new Object[] {node.getId(),type,index}),
+				node	
+			);
+		}
+
+		@Override
+		public void setModifiers(Node node, String type, int index,
+				Set<Integer> modifiers) {
+			Object args[] = new Object[modifiers.size()+3];
+			args[0] = node.getId();
+			args[1] = type;
+			args[2] = index;
+			int i = 0;
+			for(Integer I : modifiers){
+				args[i+3] = I;
+				i++;
+			}
+			send(new Command(Command.Name.SET_MODIFIERS, delegateDiagram.getName(),args),node);
+		}
+
+		@Override
+		public void setEndLabel(Edge edge, Node node, String label) {
+			send(new Command(Command.Name.SET_ENDLABEL, delegateDiagram.getName(),
+					new Object[] {edge.getId(), node.getId(), label}),
+				edge
+			);
+		}
+
+		@Override
+		public void setEndDescription(Edge edge, Node node, int index) {
+			send(new Command(Command.Name.SET_ENDDESCRIPTION, delegateDiagram.getName(),
+					new Object[] {edge.getId(), node.getId(), index}),
+				edge
+			);
+		}
+
+		@Override
+		public void translate(GraphElement ge, Point2D p, double dx, double dy) {
+			double px = 0;
+			double py = 0;
+			if(p != null){
+				px = p.getX();
+				py = p.getY();
+			}
+			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}
+							),n);
+			}else{
+				Edge e = (Edge)ge;
+				send(new Command(Command.Name.TRANSLATE_EDGE, delegateDiagram.getName(),
+						new Object[] {e.getId(), px, py, dx,dy}
+						),e);
+			}
+		}
+
+		@Override
+		public void startMove(GraphElement ge, Point2D p) {
+			/* 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 * 
+			 * the edge is clicked down. So this variable is non null only when the first       *
+			 * bend-message is sent                                                             */
+			edgeStartMovePoint = p;
+		}
+			
+		@Override
+		public void bend(Edge edge, Point2D p) {
+			/* 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);
+			else{
+				send(new Command(Command.Name.BEND, delegateDiagram.getName(),
+						new Object[] {edge.getId(),p.getX(),p.getY(),edgeStartMovePoint.getX(),edgeStartMovePoint.getY()}),
+						edge);
+				edgeStartMovePoint = null;
+			}
+		}
+
+		@Override
+		public void stopMove(GraphElement ge) {
+			if(ge instanceof Node){
+				Node n = (Node)ge;
+				send(new Command(Command.Name.STOP_NODE_MOVE, delegateDiagram.getName(),
+						new Object[] {n.getId()}),
+					n);
+			}else{
+				Edge e = (Edge)ge;
+				send(new Command(Command.Name.STOP_EDGE_MOVE, delegateDiagram.getName(),
+					new Object[] {e.getId()}),
+					e);
+			}
+		}
+		
+		private Point2D edgeStartMovePoint;
+	}
+	
+	private static class RemoteHostDiagram extends NetDiagram{
+		/**
+		 * This class wraps an existing diagram into a network diagram. 
+		 * The network diagrams returns a TreeModelNetWrap and a CollectionModelNetWrap
+		 * when the relative getters are called
+		 * @see TreeModelNetWrap, CollectionModelNetWrap
+		 * 
+		 * @param diagram the diagram to wrap
+		 * @param connectionManager a connected socket channel
+		 */
+		private RemoteHostDiagram(Diagram diagram, ClientConnectionManager connectionManager,SocketChannel channel){
+			super(diagram);
+			this.channel = channel;
+			this.connectionManager = connectionManager;
+		}
+
+		@Override
+		protected void send(Command cmd, DiagramElement element) {
+			connectionManager.addRequest(new ClientConnectionManager.SendCmdRequest(cmd, channel, element ));
+		}
+		
+		@Override
+		protected void send(Command cmd, DiagramModelTreeNode treeNode) {
+			connectionManager.addRequest(new ClientConnectionManager.SendTreeCmdRequest(cmd, channel, treeNode ));
+		}
+		
+		@Override
+		protected void send(LockMessage lockMessage){
+			connectionManager.addRequest(new ClientConnectionManager.SendLockRequest(channel, lockMessage));
+		}
+		
+		@Override
+		protected boolean receiveLockAnswer(){
+			ClientConnectionManager.Answer answer = connectionManager.getAnswer();
+			if(answer instanceof ClientConnectionManager.ErrorAnswer)
+				return false;
+			LockMessage.Name name = ((LockAnswer)answer).message.getName();
+			switch(name){
+			case YES_L :
+				return true;
+			case NO_L : 
+				return false;
+			default : 		
+				throw new RuntimeException("message not recognized: "+name.toString());
+			}
+		}
+		
+		@Override 
+		public String getLabel(){
+			return new StringBuilder(getName())
+				.append(' ').append('@').append(' ')
+				.append(channel.socket().getInetAddress().getHostAddress())
+				.toString();
+		}
+		
+		@Override
+		public SocketChannel getSocketChannel(){
+			return channel;
+		}
+		
+		@Override
+		public Object clone(){
+			throw new UnsupportedOperationException();
+		}
+		
+		private SocketChannel channel;
+		private ClientConnectionManager connectionManager;
+	}
+	
+	private static class LocalHostDiagram extends NetDiagram {
+
+		private LocalHostDiagram(Diagram diagram, SocketChannel channel, Queue<DiagramElement> diagramElements, ExceptionHandler handler) {
+			super(diagram);
+			this.channel = channel;
+			this.diagramElements = diagramElements;
+			this.exceptionHandler = handler;
+			this.protocol = ProtocolFactory.newInstance();
+		}
+
+		@Override
+		protected void send(Command cmd, DiagramElement element){
+			switch(cmd.getName()){
+			case INSERT_NODE :
+			case INSERT_EDGE : 
+			case REMOVE_NODE : 
+			case REMOVE_EDGE : 
+				diagramElements.add(element);
+				break;
+			}
+			try{
+				protocol.send(channel, cmd);
+			}catch(IOException ioe){
+				switch(cmd.getName()){
+				case INSERT_NODE :
+				case INSERT_EDGE : 
+				case REMOVE_NODE : 
+				case REMOVE_EDGE : 
+					diagramElements.remove(element);
+					break;
+				}
+				exceptionHandler.handleException(ioe);
+			}
+		}
+		
+		@Override
+		protected void send(LockMessage lockMessage) {
+			try {
+				protocol.send(channel, lockMessage);
+			} catch (IOException ioe) {
+				exceptionHandler.handleException(ioe);
+			}
+		}		
+		
+		@Override
+		protected boolean receiveLockAnswer(){
+			LockMessage answer;
+			try {
+				answer = protocol.receiveLockMessage(channel);
+			} catch (IOException ioe) {
+				exceptionHandler.handleException(ioe);
+				return false;
+			}
+			switch((LockMessage.Name)answer.getName()){
+				case YES_L :
+					return true;
+				case NO_L : 
+					return false;
+				default : 		
+					throw new RuntimeException("message not recognized: "+answer.getName().toString());
+			}
+		}
+		
+		@Override
+		protected void send(Command cmd , DiagramModelTreeNode treeNode){
+			try {
+				protocol.send(channel, cmd);
+			} catch (IOException ioe) {
+				exceptionHandler.handleException(ioe);
+			}
+		}
+		
+		@Override 
+		public String getLabel(){
+			return  getName()+" @ localhost";
+		}
+		
+		@Override
+		public SocketChannel getSocketChannel(){
+			return channel;
+		}
+		
+		SocketChannel channel;
+		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/OscProtocol.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,243 @@
+/*  
+ 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 java.io.IOException;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+
+import de.sciss.net.OSCBundle;
+import de.sciss.net.OSCMessage;
+
+/*
+ * An implementation of the Protocol interface which uses OSC messages 
+ * streamed on a TCP connection. 
+ *
+ */
+class OscProtocol implements Protocol {
+
+	OscProtocol(){
+		buffer = ByteBuffer.allocate(DEFAULT_CAPACITY);
+		codec = new CCmIOSCPacketCodec();
+	}
+	
+	@Override
+	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()];
+		args[0] = cmd.getDiagram();
+		for(int i=0; i<cmd.getArgNum();i++)
+			args[i+1] = 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);
+		}
+	}
+	
+	@Override
+	public void send(SocketChannel channel, Reply reply) throws IOException {
+		/* OSC message args = [messageLen, diagram, message ] */
+		bundle = new CCmIOSCBundle(reply.getTimestamp());
+		bundle.addPacket(new OSCMessage(
+				OSC_NAME_PREFIX+reply.getName().toString(), 
+				new Object[] {reply.getMessageLen(), reply.getDiagram() ,reply.getMessage()}
+				)
+		);
+		try{
+			writeBundle(channel);
+		}catch(IOException u){
+			throw new IOException("Could not send data to the server",u);
+		}
+	}
+	
+	@Override
+	public void send(SocketChannel channel, LockMessage lockMessage) throws IOException {
+		/* OSC message args = [diagram, lock.arg1, lock.arg2, lock.arg2, ... , lock.argN  ] */
+		bundle = new CCmIOSCBundle(lockMessage.getTimestamp());
+		Object[] args = new Object[1+lockMessage.getArgNum()];
+		args[0] = lockMessage.getDiagram();
+		for(int i=0; i<lockMessage.getArgNum();i++)
+			args[i+1] = lockMessage.getArgAt(i);
+		
+		bundle.addPacket(new OSCMessage(
+				OSC_NAME_PREFIX+lockMessage.getName().toString(), 
+				args
+				)
+		);
+		try{
+			writeBundle(channel);
+		}catch(IOException u){
+			/* give a more user friendly message */
+			throw new IOException("Could not send data to the server",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 Reply receiveReply(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 '/'
+		@SuppressWarnings("unused")
+		Integer len = (Integer)oscMessage.getArg(REPLY_LEN_INDEX); // of no use at the moment
+		return new Reply(
+				Reply.valueOf(name),
+				(String)oscMessage.getArg(REPLY_DIAGRAM_INDEX),
+				(String)oscMessage.getArg(REPLY_MESSAGE_INDEX),
+				bundle.getTimeTag());
+	}
+	
+	@Override
+	public LockMessage receiveLockMessage(SocketChannel channel) throws IOException {
+		OSCBundle bundle = readBundle(channel);
+		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;
+		for(int i=0; i< args.length;i++)
+			args[i] = oscMessage.getArg(i+offset);
+		return new LockMessage(
+				LockMessage.valueOf(name),
+				bundle.getTimeTag(),
+				(String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
+				args
+				);
+	}
+	
+	public Message receiveMessage(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 '/'
+		if(name.endsWith(Reply.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());
+			return reply;
+		}else if (name.endsWith(LockMessage.LOCK_NAME_POSTFIX)){
+			Object args[] = new Object[oscMessage.getArgCount()-1];			 
+			int offset = LOCK_DIAGRAM_INDEX + 1;
+			for(int i=0; i< args.length;i++)
+				args[i] = oscMessage.getArg(i+offset);
+			return new LockMessage(
+					LockMessage.valueOf(name),
+					bundle.getTimeTag(),
+					(String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
+					args
+					);
+		}else{ // it's a command
+			Object args[] = new Object[oscMessage.getArgCount()-1];			 
+			int offset = CMD_DIAGRAM_INDEX + 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()
+			);
+		}
+	}
+	
+	private OSCBundle readBundle(SocketChannel channel) throws IOException{
+		/* read the size of the OSC packet, first 4 bytes according to OSC specs */
+		buffer.rewind().limit(4);
+		while( buffer.hasRemaining() )
+			if( channel.read( buffer ) == -1 )
+				throw new SocketException("Connection closed by peer");
+			
+		buffer.rewind();
+		int packetSize = buffer.getInt();
+		assert(packetSize > 0 );
+		ByteBuffer b = buffer;
+		/* if the packet is very big we must allocate an ad hoc temporary big big buffer */
+		if(packetSize <= DEFAULT_CAPACITY)
+			b.rewind().limit(packetSize);
+		else	
+			b = ByteBuffer.allocate(packetSize);
+		/* 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");
+		b.rewind();
+		return (OSCBundle)codec.decode(b);
+	}
+	
+	private void writeBundle(SocketChannel channel) throws IOException{
+		ByteBuffer b = buffer;
+		buffer.clear();
+		if(bundle.getSize() + 4 > DEFAULT_CAPACITY){
+			b = ByteBuffer.allocate(bundle.getSize() + 4);
+		}
+		b.position(4);
+		bundle.encode(codec,b);
+		int len = b.position() - 4;
+		b.putInt(0, len);
+		b.flip();
+		channel.write(b);
+	}
+	
+	ByteBuffer buffer;
+	CCmIOSCBundle bundle;
+	CCmIOSCPacketCodec codec;
+	
+	private static final int DEFAULT_CAPACITY = 1024;
+	private static final char OSC_NAME_PREFIX = '/';
+	
+	
+	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 CMD_DIAGRAM_INDEX = 0;
+	private static final int LOCK_DIAGRAM_INDEX = 0;
+	/* ------------------------------------------------*/
+	private static final int REPLY_MESSAGE_INDEX = 2;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Protocol.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,45 @@
+/*  
+ 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 java.io.IOException;
+import java.nio.channels.SocketChannel;
+
+/**
+ * Objects implementing the {@code Protocol} interface will take care of 
+ * the underlying communication protocol used over TCP/IP sockets.   
+ *
+ */
+public interface Protocol {
+	void send(SocketChannel channel, Command cmd) throws IOException;
+	
+	void send(SocketChannel channel, Reply reply) throws IOException;
+	
+	void send(SocketChannel channel, LockMessage lock) 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;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ProtocolFactory.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,30 @@
+/*  
+ 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;
+
+/**
+ * The factory class to create {@code Protocol} instances. 
+ *
+ */
+public abstract class ProtocolFactory {
+    public static Protocol newInstance(){
+    	return new OscProtocol();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Reply.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,135 @@
+/*  
+ 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.InteractionLog;
+
+/**
+ * A {@code Reply} message is sent from the server to a client from which it 
+ * has just received a command. The reply acknowledges the client that the command has 
+ * successfully been executed on the server. By broadcasting the command to the other client and by 
+ * ending the reply to the client which issued the command itself the server keeps all the clients 
+ * synchronized on its diagram model. 
+ *
+ */
+public class Reply extends Message {
+	
+	public Reply(Name name, String diagram, String message, long timestamp){
+		super(timestamp,diagram);
+		this.name = name;
+		this.message = message;
+	}
+	
+	public Reply(Name name, String diagram, String message){
+		super(diagram);
+		this.name = name;
+		this.message = message;
+	}
+	
+	public Name getName() {
+		return name;
+	}
+	
+	public String getMessage() {
+		return message;
+	}
+	
+	/**
+	 * @return the length of the String message conveyed by this Reply 
+	 */
+	public int getMessageLen(){
+		return message.length();
+	}
+	
+	@Override
+	public String toString(){
+		StringBuilder builder = new StringBuilder();
+		builder.append(timestamp).append(' ').append(name).append('\n').append(message);
+		return builder.toString();
+	}
+
+	public static Name valueOf(String n){
+		Name name = Name.NONE_R;
+		try {
+			name = Name.valueOf(n);
+		}catch(IllegalArgumentException iae){
+			name.setOrigin(n); 
+		}
+		return name;
+	}
+	
+	public static void log(Reply reply){
+		if(reply.getName() != Reply.Name.TRANSLATE_EDGE_R && reply.getName() != Reply.Name.TRANSLATE_NODE_R && reply.getName() != Reply.Name.BEND_R){
+			StringBuilder builder = new StringBuilder(reply.getName().toString());
+			builder.append(' ').append(reply.getDiagram());
+			builder.append(' ').append(reply.getMessage());
+			InteractionLog.log("SERVER", "reply received", builder.toString());
+		}
+	}
+	
+	private Name name;
+	private String message;
+	private long timestamp;
+	public static final String REPLY_NAME_POSTFIX = "_R";
+	public static enum Name implements Message.MessageName {
+		NONE_R,
+		OK_R,
+		ERROR_R,
+		LIST_R,
+		GET_R,
+		INSERT_NODE_R,
+		REMOVE_NODE_R,
+		INSERT_EDGE_R,
+		REMOVE_EDGE_R,
+		SET_NODE_NAME_R,
+		SET_EDGE_NAME_R,
+		SET_PROPERTY_R,
+		SET_PROPERTIES_R,
+		CLEAR_PROPERTIES_R,
+		SET_NOTES_R,
+		ADD_PROPERTY_R,
+		REMOVE_PROPERTY_R,
+		SET_MODIFIERS_R,
+		SET_ENDDESCRIPTION_R,
+		SET_ENDLABEL_R,
+		TRANSLATE_NODE_R,
+		TRANSLATE_EDGE_R,
+		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;
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Server.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,201 @@
+/*  
+ 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 java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+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;
+
+/**
+ * The {@code Server} is a thread the user can start, which accept connections from other clients 
+ * (other users machines running the CCmI editor).
+ * When a user shares a diagram via the server, other clients 
+ * will be able to see and to modify it in real time. This is achieved by exchanging network
+ * message between the server and each client.   
+ * On a shared diagram scenario the server
+ * diagram model is considered to be the "real" one. That is, the other clients will continuously 
+ * synchronize their model with the server and even before applying a command issued by an action of 
+ * the local user, clients need first send the command to the server (which will apply the command on its own model) 
+ * and wait for an acknowledging reply. 
+ *
+ */
+public class Server extends Thread {
+	
+	public static Server createServer(){
+		if(server != null)
+			server.shutdown(server.resources.getString("log.restart"));
+		server = new Server();
+		return server;
+	}
+	
+	private Server(){
+		super("Server Thread");
+		mustSayGoodbye = false;
+		initialized = false;
+		resources = ResourceBundle.getBundle(this.getClass().getName());
+	}
+		
+	public void init(JFrame frame) throws IOException {
+		if(initialized)
+			return;
+		serverChannel = ServerSocketChannel.open();
+		selector = Selector.open();
+		diagrams = new HashMap<String,Diagram>();
+		scManager = new ServerConnectionManager(diagrams);
+		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));
+		logger.config("Server initialized, will listen on port: "+port);
+		initialized = true;
+	}
+	
+	@Override
+	public void run(){
+		logger.config(resources.getString("log.start"));
+		running = true;
+		while(!mustSayGoodbye){
+			try{
+				selector.select();
+				if(mustSayGoodbye)
+					break;
+				for (Iterator<SelectionKey> itr = selector.selectedKeys().iterator(); itr.hasNext();){
+					SelectionKey key = itr.next(); 
+					itr.remove(); 
+					
+					if(!key.isValid())
+						continue;
+					if(key.isAcceptable()){
+						SocketChannel clientChannel = serverChannel.accept();
+						clientChannel.configureBlocking(false);
+						clientChannel.register(selector, SelectionKey.OP_READ);
+					}
+					if(key.isReadable()){
+						try{
+							scManager.handleMessage((SocketChannel)key.channel());
+						}catch(IOException ioe){
+							/* Upon exception the channel is no longer considered reliable: it's cleaned up and closed */
+							SocketChannel channel = (SocketChannel)key.channel(); 
+							channel.close();
+							scManager.removeChannel(channel);
+							logger.severe(ioe.getLocalizedMessage());
+						}
+					}
+				}
+			}catch(IOException e){
+				logger.severe(e.getLocalizedMessage());
+				e.printStackTrace();
+				break;
+			}
+		}
+		cleanup();
+	}
+	
+	private void cleanup(){	
+		try {
+			/* close all the channels */
+			serverChannel.close();
+			for (Iterator<SelectionKey> itr = selector.keys().iterator(); itr.hasNext();){
+				SelectionKey key = itr.next();
+				((Closeable)key.channel()).close();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		running = false;
+	}
+	
+	public void shutdown(String msg){
+		if(msg != null)
+			NarratorFactory.getInstance().speak(resources.getString("log.shutdown")+msg);
+		shutdown();
+	}
+	
+	public void shutdown(){
+		mustSayGoodbye = true;
+		selector.wakeup();
+		server = null;
+		for(Handler h : logger.getHandlers()){
+			h.close();
+			logger.removeHandler(h);
+		}
+	}
+	
+	public Queue<DiagramElement> getLocalhostQueue(String diagramName){
+		return scManager.getLocalhostMap().get(diagramName);
+	}
+	
+	/* this is called by the event dispatching thread when the user shares a diagram */
+	public void share(Diagram diagram) throws ServerNotRunningException, DiagramAlreadySharedException{
+		String name = diagram.getName();
+		if(!running)
+			throw new ServerNotRunningException(resources.getString("exception_msg.not_run"));
+		synchronized(diagrams){
+			if(diagrams.containsKey(name)){
+				throw new DiagramAlreadySharedException(resources.getString("exception_msg.already_shared"));
+			}
+			diagrams.put(name,diagram);
+		}
+		scManager.getLocalhostMap().put(name, new ConcurrentLinkedQueue<DiagramElement>());
+		logger.info("Diagram "+name+" shared on the server");
+	}
+	
+	private static Server server;
+	public static final String DEFAULT_LOCAL_PORT = "7777";
+	public static final String DEFAULT_REMOTE_PORT = "7777";
+	static Logger logger;
+	private ServerSocketChannel serverChannel;
+	private ServerConnectionManager scManager;
+	private Selector selector;
+	private Map<String, Diagram> diagrams;
+	private ResourceBundle resources;
+	private boolean initialized;
+	private volatile boolean mustSayGoodbye;
+	private boolean running;	
+	
+	static {
+		logger = Logger.getLogger(Server.class.getCanonicalName());
+		logger.setUseParentHandlers(false);
+		logger.setLevel(Level.CONFIG);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Server.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,9 @@
+
+log.start=Server started...
+log.restart=Restarting server...
+log.shutdown=Server shutdown:
+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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,491 @@
+/*  
+ 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 java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.BufferedOutputStream;
+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.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+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.gui.Diagram;
+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.persistence.PersistenceManager;
+
+
+
+/* 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){
+		this.diagrams = diagrams;
+		diagramChannelAllocations = new HashMap<Diagram,Set<SocketChannel>>();
+		localhostDiagramElementQueue = new ConcurrentHashMap<String,Queue<DiagramElement>>();
+		protocol = ProtocolFactory.newInstance();
+		lockManager = new ServerLockManager();
+	}
+	
+	/**
+	 * remove the channel and the locks related to it from the inner data structures 
+	 * @param channel the channel to remove
+	 */
+	public void removeChannel(SocketChannel channel){
+		String diagramName = null;
+		for(Map.Entry<Diagram,Set<SocketChannel>> entry : diagramChannelAllocations.entrySet()){
+			if(entry.getValue().remove(channel)){
+				diagramName = entry.getKey().getName();
+			}
+		}
+		lockManager.removeLocks(channel, diagramName);
+	}
+	
+	public void handleMessage(SocketChannel channel) throws IOException{
+		Message message = protocol.receiveMessage(channel);
+		if(message instanceof Command){
+			handleCommand((Command)message, channel);
+		}else {
+			handleLockMessage((LockMessage)message,channel);
+		}
+	}
+	
+	private void handleLockMessage(LockMessage lockMessage, SocketChannel channel) throws IOException{
+		Lock lock = LockMessageConverter.getLockFromMessageName((LockMessage.Name)lockMessage.getName());
+		String name = lockMessage.getName().toString();
+		String diagramName = lockMessage.getDiagram(); 
+		
+		/* 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());
+		}
+		/* the tree node has been deleted, lock cannot be granted */
+		if(treeNode == null){
+			replyLockMessage(channel,diagramName,false);
+			return;
+		}
+		//System.out.println("Lock message received: " + name +" diagram:"+diagramName+" treenode:"+treeNode.getName() );
+		
+		/* 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);	
+		}else{ // yield lock 
+			lockManager.releaseLock(treeNode, lock, channel,diagramName);
+		}
+	}
+	
+	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);
+		}
+		node = null;
+		edge = null;
+		boolean broadcast = true;
+
+		/* 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 : 	
+			localChannel = channel;
+			Set<SocketChannel> list = new HashSet<SocketChannel>();
+			list.add(localChannel);
+			diagramChannelAllocations.put(diagram, list);
+			broadcast = false;
+			break;
+		case LIST :
+			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()));
+			broadcast = false;
+			break;
+		case GET :
+			try{
+				if(diagram == null){
+					protocol.send(channel, new Reply(Reply.Name.ERROR_R,diagramName,"Diagram "+diagramName+" does not exists"));
+					break;
+				}
+				ByteArrayOutputStream out = new ByteArrayOutputStream();
+				PersistenceManager.encodeDiagramInstance(diagram, new BufferedOutputStream(out));
+				try{
+					protocol.send(channel, new Reply(Reply.Name.GET_R,diagramName,out.toString("UTF-8")));
+				}catch(IOException ioe){
+					throw ioe;
+				}
+				diagramChannelAllocations.get(diagram).add(channel);
+				broadcast = false;
+			}catch(Exception e){
+				// log and discard the packet
+				Server.logger.severe(e.getMessage());
+			}
+			break;
+		case INSERT_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 wanted to insert */		 
+				node = (Node)localhostDiagramElementQueue.get(diagramName).poll();
+			}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);
+				}
+			}
+			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();
+			}else{
+				synchronized(diagram.getCollectionModel().getMonitor()){
+					node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+				}
+			}
+			/* 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));
+			} 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"));
+			break;
+		case INSERT_EDGE :
+			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
+					}
+				}
+			}
+			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){
+				/* 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((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+				}
+			}
+			/* 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));
+			} catch (Exception e) {
+				throw new RuntimeException(e); // must never happen
+			}
+			/* 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"));
+			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))));
+			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))));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.SET_NODE_NAME_R,diagramName,"Name set to "+ cmd.getArgAt(1)));
+			break;	
+		case SET_PROPERTY :
+			synchronized(diagram.getCollectionModel().getMonitor()){
+				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			}
+			SwingUtilities.invokeLater(new CommandExecutor.SetProperty(
+					node,
+					(String)cmd.getArgAt(1),
+					(Integer)cmd.getArgAt(2),
+					(String)cmd.getArgAt(3)
+					));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.SET_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(2)+ " set to "+ cmd.getArgAt(3)));
+			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));
+			SwingUtilities.invokeLater(new CommandExecutor.SetProperties(
+					node,
+					properties
+					));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.SET_PROPERTIES_R,diagramName,"Properties for " + node.getName()+ " set to "+ cmd.getArgAt(1)));
+			break;	
+		case CLEAR_PROPERTIES :
+			synchronized(diagram.getCollectionModel().getMonitor()){
+				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			}
+			SwingUtilities.invokeLater(new CommandExecutor.ClearProperties(node));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.CLEAR_PROPERTIES_R,diagramName,"Propertis of Node "+ node.getName() +" cleared"));
+			break;
+		case SET_NOTES :
+			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));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.SET_NOTES_R,diagramName,"Notes for " + treeNode.getName() + " successfully updated"));
+			break;
+		case ADD_PROPERTY :
+			synchronized(diagram.getCollectionModel().getMonitor()){
+				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			}
+			SwingUtilities.invokeLater(new CommandExecutor.AddProperty(
+					node,
+					(String)cmd.getArgAt(1), 
+					(String)cmd.getArgAt(2)
+					));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.ADD_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " added to "+ cmd.getArgAt(1)));
+			break;
+		case REMOVE_PROPERTY :
+			synchronized(diagram.getCollectionModel().getMonitor()){
+				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			}
+			SwingUtilities.invokeLater(new CommandExecutor.RemoveProperty(
+					node, 
+					(String)cmd.getArgAt(1),
+					(Integer)cmd.getArgAt(2)));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.REMOVE_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " of type "+ cmd.getArgAt(1)+" removed"));
+			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));
+				}
+			}
+			SwingUtilities.invokeLater(new CommandExecutor.SetModifiers(
+					node,
+					(String)cmd.getArgAt(1),
+					(Integer)cmd.getArgAt(2),
+					indexes));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.SET_MODIFIERS_R,diagramName,"Modifiers for " + cmd.getArgAt(1)+ " successfully set"));
+			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)));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.SET_ENDLABEL_R,diagramName,"Endlabel set to "+ cmd.getArgAt(2) +" for edge "+edge.getName()));
+			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());
+			}		
+			SwingUtilities.invokeLater(new CommandExecutor.SetEndDescription(
+					edge,
+					node, 
+					(Integer)cmd.getArgAt(2)
+					));
+			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()));
+			break;	
+		case TRANSLATE_NODE :
+			synchronized(diagram.getCollectionModel().getMonitor()){
+				node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
+			}
+			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)
+					));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.TRANSLATE_NODE_R,diagramName,"Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")"));
+			break;
+		case TRANSLATE_EDGE :
+			synchronized(diagram.getCollectionModel().getMonitor()){
+				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			}
+			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)
+					));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.TRANSLATE_EDGE_R,diagramName,"Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")"));
+			break;
+		case BEND :
+			synchronized(diagram.getCollectionModel().getMonitor()){
+				edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
+			}
+			Point2D bendStart = null;
+			if(cmd.getArgNum() == 5){
+				bendStart = new Point2D.Double((Double)cmd.getArgAt(3),(Double)cmd.getArgAt(4));
+			}
+			SwingUtilities.invokeLater(new CommandExecutor.Bend(
+					edge,
+					new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
+					bendStart
+			));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.BEND_R,diagramName,"Bend at point: ("+(Double)cmd.getArgAt(1)+","+(Double)cmd.getArgAt(1)+")"));
+			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));
+			if(channel != localChannel)
+				protocol.send(channel, new Reply(Reply.Name.STOP_EDGE_MOVE_R,diagramName,"Undo straight bends"));
+			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));
+			if(channel != localChannel){
+				protocol.send(channel, new Reply(Reply.Name.STOP_NODE_MOVE_R,diagramName,"Stop node move"));
+			}
+			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);
+			}
+		}
+	}
+	
+	public Map<String, Queue<DiagramElement>> getLocalhostMap() {
+		return localhostDiagramElementQueue;
+	}
+	
+	private void replyLockMessage(SocketChannel channel, String diagramName,boolean yes) throws IOException{
+		protocol.send(channel, new LockMessage(
+				yes ? LockMessage.Name.YES_L : LockMessage.Name.NO_L,
+				diagramName,
+				-1
+		));
+	}
+	
+	private Diagram diagram;
+	private Node node;
+	private Edge edge;
+	private DiagramElement de;
+	private DiagramModelTreeNode treeNode;
+	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;
+	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;
+	/* 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerLockManager.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,370 @@
+/*  
+ 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 java.nio.channels.SocketChannel;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+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.EdgeReferenceHolderMutableTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode;
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.Lock;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+
+/**
+ * 
+ * This class keeps track of the objects currently locked by the users.
+ * Locking is done by inserting a lock entry into a list of entries. All lock types have one or more dependencies. 
+ * A dependency is a lock on the same or another tree node, which prevent the lock from being acquired. 
+ * The dependency lock can also be of another type of the lock we're trying to acquire. 
+ * For example if I want to change the arrow head of edge E, before inserting a arrow-head lock in the list  
+ * I must be sure nobody else has already entered a delete-lock for E, as it means that user is about to 
+ * delete the edge I want to change the arrow of. Likewise if I want to delete a node I must be sure 
+ * nobody else inserted a note-lock on a edge reference laying within that node in the tree as that edge reference 
+ * will go after the node is deleted. If a user tries to acquire a lock and one or more dependencies 
+ * have been already inserted in the list by other users then the lock won't be granted. I no dependency 
+ * exist in the list the lock entry is created and inserted.
+ * 
+ * 
+ * Lock dependencies (unless differently specified the dependency lock is on the same tree node): 
+ * DELETE : DELETE,ARROW_HEAD,END_LABEL,MOVE,MUST_EXIST,NAME,NOTES, BOOKMARK,PROPERTIES, 
+ *          DELETE on two ended edges if this is a node, NOTES and BOOKMARK on those tree node 
+ *          that will be deleted as a result of this action.
+ * EDGE_END : EDGE_END, DELETE 
+ * MOVE : MOVE, DELETE
+ * MUST_EXIST : DELETE 	
+ * NAME : NAME, DELETE
+ * NOTES : NOTES, DELETE, DELETE on Objects whose deletion would entail the deletion of this tree node
+ *         PROPERTIES if this is a subtree of a property node. 
+ * BOOKMARK : DELETE 
+ * PROPERTIES : PROPERTIES, DELETE
+ * 
+ *
+ */
+class ServerLockManager { 
+	public ServerLockManager(){
+		locksMap = new HashMap<String,List<LockEntry>>();
+	}
+	
+	/* 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){
+		for(LockEntry lockEntry : locks){
+			if(lockEntry.treeNode.equals(treeNode) && lockEntry.lock == lock && !lockEntry.channel.equals(channel))
+				return true;
+		}
+		return false;
+	}
+	
+	/* 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){
+		for(LockEntry lockEntry : locks){
+			if(lockEntry.treeNode.equals(treeNode) && (lockEntry.lock == lock1 || lockEntry.lock == lock2) && !lockEntry.channel.equals(channel) )
+				return true;
+		}
+		return false;
+	}
+	
+	/* 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){
+		/* 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)  
+			for(LockEntry lockEntry : locks){
+				/* if the two elements are different, there is no possible clash. Go ahead */
+				if(!lockEntry.treeNode.equals(treeNode))  
+					continue; 
+				/* locks are reentrant, that is if a client requests a lock he's already got  * 
+				 * then he's automatically granted for that lock                              */
+				if(lockEntry.channel.equals(channel)){
+					if(lock.equals(lockEntry.lock)){
+						return true;
+					}
+					continue; //if the clients has ad different lock then just go ahead
+				}
+				/* DELETE depends on all the other locks and all the other locks depend  on it  */ 
+				/* (bear in mind that at this point of the code lockEntry.treeNode == treeNode) */
+				if(lock == Lock.DELETE||lockEntry.lock == Lock.DELETE)  
+					return false;
+				/* unless the lock is of type MUST_EXIST, if someone else has the desired lock, then the client   */
+				/* cannot get it that is, only MUST_EXIST can be shared by different clients at the same time     */
+				if(lock != Lock.MUST_EXIST && lock == lockEntry.lock)
+					return false;
+			}
+		
+		/* delete-locks on nodes and edges involved with treeNode will prevent from acquiring the lock as treeNode might     */
+		/* no longer exist after the editing. See the following comments to figure out what "involved" means in this context */ 
+		if(lock == Lock.NOTES || lock == Lock.BOOKMARK){
+			/* no diagram element along the path from root to this treeNode must be delete-locked or properties-locked */
+			/* (as editing a property might mean delete some tree nodes and, maybe, this tree node)                    */
+			for(TreeNode tn : treeNode.getPath()){
+				if(tn instanceof DiagramElement)
+					if(lockExists((DiagramElement)tn,Lock.DELETE,Lock.PROPERTIES,locks,channel))
+						return false;
+			}
+			/* if note-locking a reference tree node, the referred diagram element must not be delete-locked */
+			/* as the reference will go as well after the diagram element will be deleted                    */
+			if(treeNode instanceof NodeReferenceMutableTreeNode){
+				NodeReferenceMutableTreeNode referenceNode = (NodeReferenceMutableTreeNode)treeNode; 
+				if(lockExists(referenceNode.getNode(),Lock.DELETE,locks,channel))
+					return false;
+			}
+			if(treeNode instanceof EdgeReferenceMutableTreeNode){
+				EdgeReferenceMutableTreeNode referenceNode = (EdgeReferenceMutableTreeNode)treeNode; 
+				if(lockExists(referenceNode.getEdge(),Lock.DELETE,locks,channel))
+					return false;
+			}
+			/* if note locking an edge reference tree holder which has only one child, we cannot grant    *
+			 * the lock if the referred edge is delete-locked as the holder will be deleted as well after *
+			 * the eventual deletion of the edge                                                          */
+			if(treeNode instanceof EdgeReferenceHolderMutableTreeNode && treeNode.getChildCount() == 1){
+				EdgeReferenceMutableTreeNode referenceNode = (EdgeReferenceMutableTreeNode)treeNode.getChildAt(0);
+				if(lockExists(referenceNode.getEdge(),Lock.DELETE,locks,channel))
+					return false;
+			}
+		}
+		
+		if(lock == Lock.DELETE){
+			/* 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))  
+					return false;
+			}
+			
+			if(treeNode instanceof Node){
+				Node n = (Node)treeNode;
+				/* if we want to delete a Node we must get the lock on each attached *
+				 * edge as they will be deleted as well  	                         */
+				for(int i =0; i< n.getEdgesNum();i++){
+					Edge e = n.getEdgeAt(i);
+					if(lockExists(e,Lock.DELETE,locks,channel))
+						return false;
+					/* In order to delete-lock a Node, no referee must be bookmark/notes-locked. The referees         *
+					 * are the  NodeReferenceTreeNode's pointing to this Node */
+					for(int j=0;j<e.getChildCount();j++){
+						NodeReferenceMutableTreeNode nodeRef = (NodeReferenceMutableTreeNode)e.getChildAt(j);
+						if(nodeRef.getNode().equals(n))
+							if(lockExists(nodeRef,Lock.NOTES,Lock.BOOKMARK,locks,channel)) 
+								return false;
+					}
+				}
+			}
+			
+			if(treeNode instanceof Edge){
+				/* for each node check whether the reference to this edge is notes-locked */
+				Edge e = (Edge)treeNode;
+				for(int i=0;i<e.getNodesNum();i++){
+					Node n = e.getNodeAt(i);
+					for(int j=0;j<n.getChildCount();j++){
+						if(n.getChildAt(j) instanceof EdgeReferenceHolderMutableTreeNode){
+							EdgeReferenceHolderMutableTreeNode refHolder = (EdgeReferenceHolderMutableTreeNode)n.getChildAt(j); 
+							/* someone else is editing notes on the reference holder and it has only one child         */
+							/* which means it will be deleted after the edge deletion, the lock cannot be granted then */ 
+							if((refHolder.getChildCount() == 1) && lockExists(refHolder,Lock.NOTES,Lock.BOOKMARK,locks,channel))
+								return false;
+							/* if a reference tree node pointing to this edge is notes-locked, the edge can't be deleted */
+							for(int k=0;k<refHolder.getChildCount();k++){
+								EdgeReferenceMutableTreeNode edgeRef = (EdgeReferenceMutableTreeNode)refHolder.getChildAt(k);
+								if(lockExists(edgeRef,Lock.NOTES,Lock.BOOKMARK,locks,channel))  
+									return false;
+							}
+						}
+					}
+				}
+			}
+		}
+		
+		/* all the checks have been passed, the client definitely deserves the lock now */
+		return true;
+	}
+	
+	/**
+	 * Request an editing lock for a tree node 
+	 * 
+	 * @param treeNode : the treeNode the caller is trying to lock
+	 * @param lock : the type of lock requested 
+	 * @param channel : the channel works as a unique identifier for the clients
+	 * @return true if the lock is successfully granted, or false otherwise (because of another client 
+	 *   holding a lock which clashes with this request) 
+	 */
+	public static ServerLockManager singletonLockManager;
+	public boolean requestLock(DiagramModelTreeNode treeNode, Lock lock, SocketChannel channel,String diagramName){
+		List<LockEntry> locks = locksMap.get(diagramName);
+		if(locks == null){
+			/* if no object in the diagram has ever been locked      */
+			/*  there is no entry in the map and one must be created */
+			locks = new LinkedList<LockEntry>();
+			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                      */
+		if(lock == Lock.DELETE && treeNode instanceof Node){
+			Node n = (Node)treeNode;
+			for(int i=0; i<n.getEdgesNum();i++){
+				if(n.getEdgeAt(i).getNodesNum() > 2)
+					continue;
+				boolean succeeded = requestLock(n.getEdgeAt(i),Lock.DELETE,channel,diagramName);
+				if(!succeeded){
+					/* release the previously acquired locks and return a failure */
+					for(int j=0;j<i;j++){
+						if(n.getEdgeAt(j).getNodesNum() == 2)
+							releaseLock(n.getEdgeAt(j),Lock.DELETE,channel,diagramName);
+					}
+					return false;
+				}
+			}
+		}
+		
+		if(!checkLockDependencies(treeNode,lock,channel,locks)){
+			if(lock == Lock.DELETE && treeNode instanceof Node){
+				Node n = (Node)treeNode;
+				for(int j=0;j<n.getEdgesNum();j++){
+					if(n.getEdgeAt(j).getNodesNum() == 2)
+						releaseLock(n.getEdgeAt(j),Lock.DELETE,channel,diagramName);
+				}
+			}
+			return false;
+		}
+		locks.add(new LockEntry(treeNode,lock,channel));
+		//System.out.println(lockStatusDescription(diagramName));
+		return true;
+	}
+	/**
+	 * Release a lock previously acquired 
+	 * @param treeNode the tree node, whose lock is getting released
+	 * @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
+	 */
+	public void releaseLock(DiagramModelTreeNode treeNode, Lock lock, SocketChannel channel, String diagramName){
+		List<LockEntry> locks = locksMap.get(diagramName);
+		Iterator<LockEntry> iterator = locks.iterator();
+		while(iterator.hasNext()){
+			LockEntry entry = iterator.next();
+			if(entry.treeNode.equals(treeNode) && entry.lock == lock && entry.channel == channel){
+				iterator.remove();
+				if(lock == Lock.DELETE && treeNode instanceof Node)
+					continue; // we have to check for attached edges which must be unlocked too 
+				else
+					break;// if ain't a delete lock, we found what looking for and we can stop
+			}
+			/* if a delete lock, we have to check for attached edges which must be unlocked too */
+			if(lock == Lock.DELETE && entry.treeNode instanceof Edge && treeNode instanceof Node){
+				Edge e = (Edge)entry.treeNode; 
+				if(e.getNodesNum() == 2){
+					if(e.getNodeAt(0).equals(treeNode) || e.getNodeAt(1).equals(treeNode)){
+						iterator.remove();
+					}
+				}
+			}
+		}
+		//System.out.println(lockStatusDescription(diagramName));
+	}
+	
+	/**
+	 * Removes all the locks related to a diagram element, to be called when a diagram element is deleted.
+	 * If the diagram element is a Node all the locks of the two-ended edges attached to it will be 
+	 * removed as well.    
+	 * @param element the diagram element whose locks must be removed 
+	 * @param diagramName the diagram the element has to be removed from
+	 */
+	public void removeLocks(DiagramElement element, String diagramName){
+		List<LockEntry> locks = locksMap.get(diagramName);
+		if(locks == null)
+			return;
+		Iterator<LockEntry> iterator = locks.iterator();
+		boolean isNode = (element instanceof Node);
+		while(iterator.hasNext()){
+			LockEntry entry = iterator.next();
+			if(entry.treeNode.equals(element)){
+				iterator.remove();
+			}
+			/* remove the lock id it's a two ended edges locks attached to this Node */
+			if(isNode && entry.treeNode instanceof Edge){
+				Edge e = (Edge)entry.treeNode;
+				if(e.getNodesNum() > 2)
+					continue;
+				if(e.getNodeAt(0).equals(element) || e.getNodeAt(1).equals(element)){
+					iterator.remove();
+				}
+			}
+		}
+	}
+	
+	/**
+	 * remove all the locks acquired by a client, this is normally called when a client disconnects
+	 * @param channel the channel uniquely identifying the client
+	 * @param diagramName the name of the diagram the channel is related to
+	 */
+	public void removeLocks(SocketChannel channel, String diagramName){
+		if(!locksMap.containsKey(diagramName))
+			/* this can happen if a client connects and downloads the diagram list *
+			 * and then disconnects without opening any diagram                    */
+			return;
+		Iterator<LockEntry> itr = locksMap.get(diagramName).iterator();
+		while(itr.hasNext())
+			if(itr.next().channel.equals(channel))
+					itr.remove();
+	}
+	
+	public String lockStatusDescription(String diagramName){
+		StringBuilder builder = new StringBuilder();
+		if(locksMap.containsKey(diagramName)){
+			List<LockEntry> locks = locksMap.get(diagramName);
+			builder.append(diagramName).append('\n');
+			for(LockEntry entry : locks){
+				builder.append(entry.channel.socket().getInetAddress().getHostAddress()).append(' ').
+					append(entry.lock).append(' ').
+					append(entry.treeNode.getName()).append('\n');
+			}
+		}
+		return builder.toString();
+	}
+	
+	public void clearAllLocks(){ // should not be used unless in a debugging session 
+		locksMap = new HashMap<String,List<LockEntry>>();
+	}
+	
+	private Map<String,List<LockEntry>> locksMap;
+	
+	private static class LockEntry {
+		public LockEntry(DiagramModelTreeNode treeNode,Lock lock,SocketChannel channel) {
+			this.channel = channel;
+			this.lock = lock;
+			this.treeNode = treeNode;
+		}
+		
+		public SocketChannel channel;
+		public Lock lock;
+		public DiagramModelTreeNode treeNode;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerNotRunningException.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,31 @@
+/*  
+ 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;
+
+/**
+ * Exception thrown when the user tries to share a diagram before starting the server
+ *
+ */
+@SuppressWarnings("serial")
+public class ServerNotRunningException extends DiagramShareException {
+	ServerNotRunningException(String msg){
+		super(msg);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/ArrowHead.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,131 @@
+/*  
+ 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/)
+
+ 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.simpletemplate;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Stroke;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.io.IOException;
+
+/**
+   This class defines arrowheads of diverse shapes.
+*/
+public enum ArrowHead {
+	TAIL("Tail"),
+	TRIANGLE("Triangle"),
+    BLACK_TRIANGLE("Black Triangle"),
+    V("V"),
+	HALF_V("Half V"),
+	DIAMOND("Diamond"),
+	BLACK_DIAMOND("Black Diamond");
+	
+   private ArrowHead(String name) {
+	   this.lowerCaseName = name;
+   }
+   
+   public static ArrowHead getArrowHeadFromString(String arrowHeadName) throws IOException{
+	   ArrowHead h;
+	   String name = arrowHeadName.toUpperCase().replace(" ", "_");
+	   try {
+		   h = ArrowHead.valueOf(name);
+	   }catch (IllegalArgumentException e){
+		   throw new IOException(e);
+	   }
+	   return h;
+   }
+   
+   @Override
+   public String toString(){
+	   return lowerCaseName;
+   }
+   
+   /**
+      Draws the arrowhead.
+      @param g2 the graphics context
+      @param p a point on the axis of the arrow head
+      @param q the end point of the arrow head
+   */
+   public void draw(Graphics2D g2, Point2D p, Point2D q){
+      GeneralPath path = getPath(p, q);
+      Color oldColor = g2.getColor();
+      if (this == BLACK_DIAMOND || this == BLACK_TRIANGLE)
+         g2.setColor(Color.BLACK);
+      else
+         g2.setColor(Color.WHITE);
+      g2.fill(path);
+      g2.setColor(oldColor);
+      Stroke oldStroke = g2.getStroke();
+      g2.setStroke(new BasicStroke());
+      g2.draw(path);
+      g2.setStroke(oldStroke);
+   }
+
+   /**
+      Gets the path of the arrowhead
+      @param p a point on the axis of the arrow head
+      @param q the end point of the arrow head
+      @return the path
+   */
+   public GeneralPath getPath(Point2D p, Point2D q){
+      GeneralPath path = new GeneralPath();
+      final double ARROW_ANGLE = Math.PI / 6;
+      final double ARROW_LENGTH = 10;
+
+      double dx = q.getX() - p.getX();
+      double dy = q.getY() - p.getY();
+      double angle = Math.atan2(dy, dx);
+      double x1 = q.getX() 
+         - ARROW_LENGTH * Math.cos(angle + ARROW_ANGLE);
+      double y1 = q.getY() 
+         - ARROW_LENGTH * Math.sin(angle + ARROW_ANGLE);
+      double x2 = q.getX() 
+         - ARROW_LENGTH * Math.cos(angle - ARROW_ANGLE);
+      double y2 = q.getY() 
+         - ARROW_LENGTH * Math.sin(angle - ARROW_ANGLE);
+
+      path.moveTo((float)q.getX(), (float)q.getY());
+      path.lineTo((float)x1, (float)y1);
+      if (this == V)
+      {
+         path.moveTo((float)x2, (float)y2);
+         path.lineTo((float)q.getX(), (float)q.getY());
+      }
+      else if (this == TRIANGLE || this == BLACK_TRIANGLE)
+      {
+         path.lineTo((float)x2, (float)y2);
+         path.closePath();                  
+      }
+      else if (this == DIAMOND || this == BLACK_DIAMOND)
+      {
+         double x3 = x2 - ARROW_LENGTH * Math.cos(angle + ARROW_ANGLE);
+         double y3 = y2 - ARROW_LENGTH * Math.sin(angle + ARROW_ANGLE);
+         path.lineTo((float)x3, (float)y3);
+         path.lineTo((float)x2, (float)y2);
+         path.closePath();         
+      }      
+      return path;
+   }
+   
+   private String lowerCaseName;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/CircleNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,138 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+
+/**
+ *
+ * A cricle shaped diagram node.
+ *
+ */
+@SuppressWarnings("serial")
+public class CircleNode extends EllipticalNode {
+
+	public CircleNode(String typeName, NodeProperties properties) {
+		super(typeName, properties);
+		dataDisplayBounds = (Rectangle2D.Double)getMinBounds();
+		cShape = new Ellipse2D.Double();
+		cShape.setFrame(dataDisplayBounds);
+	}
+	
+	@Override
+	public Rectangle2D getMinBounds(){
+		Rectangle2D r = super.getMinBounds();
+		r.setFrame(r.getX(), r.getY(), r.getHeight(), r.getHeight());
+		return (Rectangle2D)r;
+	}
+	
+	@Override
+	public Rectangle2D getBounds(){
+		return cShape.getBounds2D();
+	}
+	
+	@Override
+	public Shape getShape(){
+		return cShape;
+	}
+	
+	@Override
+	public InputStream getSound(){
+		return sound;
+	}
+	
+	@Override
+	protected void translateImplementation(Point2D p, double dx, double dy){
+		/* if we clicked on a property node, just move that one */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList)
+				if(pn.contains(p)){
+					pn.translate(dx, dy);
+					return;
+				}
+		cShape.setFrame(cShape.getX() + dx,
+		         cShape.getY() + dy, 
+		         cShape.getWidth(), 
+		         cShape.getHeight());
+		super.translateImplementation(p,dx, dy);
+	}
+	
+	@Override
+	protected void reshapeInnerProperties(List<String> insidePropertyTypes){
+		super.reshapeInnerProperties(insidePropertyTypes);
+		double diffwh = dataDisplayBounds.getWidth() - dataDisplayBounds.getHeight();
+		Rectangle2D.Double r = new Rectangle2D.Double();
+		if(diffwh > 0){
+			r.setFrame(dataDisplayBounds.getX(),dataDisplayBounds.getY()-diffwh/2,dataDisplayBounds.getWidth(),dataDisplayBounds.getWidth());
+		} else if(diffwh < 0){
+			r.setFrame(dataDisplayBounds.getX()+diffwh/2,dataDisplayBounds.getY(),dataDisplayBounds.getHeight(),dataDisplayBounds.getHeight());
+		}else{
+			r.setFrame(dataDisplayBounds.getX(),dataDisplayBounds.getY(),dataDisplayBounds.getHeight(),dataDisplayBounds.getHeight());
+		}
+		cShape.setFrame(super.anyInsideProperties() ? getOutBounds(r) : r);
+	}
+	
+	@Override
+	public void decode(Document doc, Element nodeTag) throws IOException{
+		super.decode(doc, nodeTag);
+		double diffwh = dataDisplayBounds.getWidth() - dataDisplayBounds.getHeight();
+		Rectangle2D.Double r = new Rectangle2D.Double();
+		if(diffwh > 0){
+			r.setFrame(dataDisplayBounds.getX(),dataDisplayBounds.getY()-diffwh/2,dataDisplayBounds.getWidth(),dataDisplayBounds.getWidth());
+		} else if(diffwh < 0){
+			r.setFrame(dataDisplayBounds.getX()+diffwh/2,dataDisplayBounds.getY(),dataDisplayBounds.getHeight(),dataDisplayBounds.getHeight());
+		}else{
+			r.setFrame(dataDisplayBounds.getX(),dataDisplayBounds.getY(),dataDisplayBounds.getHeight(),dataDisplayBounds.getHeight());
+		}
+		cShape.setFrame(super.anyInsideProperties() ? getOutBounds(r) : r);
+
+	}
+	
+	@Override
+	public ShapeType getShapeType(){
+		return ShapeType.Circle;
+	}
+	
+	@Override
+	public Object clone(){
+		CircleNode n = (CircleNode)super.clone();
+		n.cShape = (Ellipse2D.Double)cShape.clone();
+		return n;
+	}
+
+	private Ellipse2D.Double cShape;
+	private static InputStream sound;
+	static{
+		sound = CircleNode.class.getResourceAsStream("audio/Circle.mp3");
+		SoundFactory.getInstance().loadSound(sound);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EdgeDrawSupport.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,163 @@
+/*  
+ 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/)
+
+ 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.simpletemplate;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.JLabel;
+
+import uk.ac.qmul.eecs.ccmi.gui.GraphPanel;
+
+
+/**
+ * Provides static methods to draw a {@code SimpleShapeEdge} on a {@code Graphics}  
+ *
+ */
+public abstract class EdgeDrawSupport {
+	/**
+	Draws a string.
+	   @param g2 the graphics context
+	   @param p an endpoint of the segment along which to
+	   draw the string
+	   @param q the other endpoint of the segment along which to
+	   draw the string
+	   @param s the string to draw
+	   @param center true if the string should be centered
+	   along the segment 
+	 * 
+	 */
+
+	public static void drawString(Graphics2D g2, //FIXME ArrowHead class should be in the same package
+			Point2D p, Point2D q, ArrowHead arrow, String s, boolean center){
+		if (s == null || s.length() == 0) return;
+		label.setText("<html>" + s + "</html>");
+		label.setFont(g2.getFont());
+		Dimension d = label.getPreferredSize();      
+		label.setBounds(0, 0, d.width, d.height);
+
+		Rectangle2D b = getStringBounds(g2, p, q, arrow, s, center);
+
+		Color oldColor = g2.getColor();
+		g2.setColor(g2.getBackground());
+		g2.fill(b);
+		g2.setColor(oldColor);
+
+		g2.translate(b.getX(), b.getY());
+		label.paint(g2);
+		g2.translate(-b.getX(), -b.getY());        
+	}
+
+	public static void drawMarker(Graphics2D g2, Point2D p, Point2D q){
+		Point2D attach = q;
+		if (p.getX() > q.getX()){ 
+			drawMarker(g2, q, p);
+			return;
+		}
+		attach = new Point2D.Double((p.getX() + q.getX()) / 2,
+				(p.getY() + q.getY()) / 2);
+		Color oldColor = g2.getColor();
+		g2.setColor(GraphPanel.GRABBER_COLOR);
+		g2.fill(new Rectangle2D.Double(attach.getX() - MARKER_SIZE / 2, attach.getY() - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE));
+		g2.setColor(oldColor);
+	}
+
+	/**
+	   Computes the attachment point for drawing a string.
+	   @param g2 the graphics context
+	   @param p an endpoint of the segment along which to
+	   draw the string
+	   @param q the other endpoint of the segment along which to
+	   draw the string
+	   @param b the bounds of the string to draw
+	   @param center true if the string should be centered
+	   along the segment
+	   @return the point at which to draw the string
+	 */
+	private static Point2D getAttachmentPoint(Graphics2D g2, 
+			Point2D p, Point2D q, ArrowHead arrow, Dimension d, boolean center){    
+		final int GAP = 3;
+		double xoff = GAP;
+		double yoff = -GAP - d.getHeight();
+		Point2D attach = q;
+		if (center){
+			if (p.getX() > q.getX()){ 
+				return getAttachmentPoint(g2, q, p, arrow, d, center); 
+			}
+			attach = new Point2D.Double((p.getX() + q.getX()) / 2, 
+					(p.getY() + q.getY()) / 2);
+			if (p.getY() < q.getY())
+				yoff =  - GAP - d.getHeight();
+			else if (p.getY() == q.getY())
+				xoff = -d.getWidth() / 2;
+			else
+				yoff = GAP;
+		}
+		else 
+		{
+			if (p.getX() < q.getX()){
+				xoff = -GAP - d.getWidth();
+			}
+			if (p.getY() > q.getY()){
+				yoff = GAP;
+			}
+			if (arrow != null){
+				Rectangle2D arrowBounds = arrow.getPath(p, q).getBounds2D();
+				if (p.getX() < q.getX()){
+					xoff -= arrowBounds.getWidth();
+				}
+				else{
+					xoff += arrowBounds.getWidth();
+				}
+			}
+		}
+		return new Point2D.Double(attach.getX() + xoff, attach.getY() + yoff);
+	}
+
+	/**
+		   Computes the extent of a string that is drawn along a line segment.
+		   @param g2 the graphics context
+		   @param p an endpoint of the segment along which to
+		   draw the string
+		   @param q the other endpoint of the segment along which to
+		   draw the string
+		   @param s the string to draw
+		   @param center true if the string should be centered
+		   along the segment
+		   @return the rectangle enclosing the string
+	 */
+	private static Rectangle2D getStringBounds(Graphics2D g2, 
+			Point2D p, Point2D q, ArrowHead arrow, String s, boolean center){
+		if (g2 == null) return new Rectangle2D.Double();
+		if (s == null || s.equals("")) return new Rectangle2D.Double(q.getX(), q.getY(), 0, 0);
+		label.setText("<html>" + s + "</html>");
+		label.setFont(g2.getFont());
+		Dimension d = label.getPreferredSize();
+		Point2D a = getAttachmentPoint(g2, p, q, arrow, d, center);
+		return new Rectangle2D.Double(a.getX(), a.getY(), d.getWidth(), d.getHeight());
+	}
+
+	private static final int MARKER_SIZE = 7;
+	private static JLabel label = new JLabel();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EllipticalNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,153 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.gui.Direction;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+
+/**
+ *
+ * An elliptical shaped diagram node.
+ *
+ */
+@SuppressWarnings("serial")
+public class EllipticalNode extends SimpleShapeNode {
+
+	public EllipticalNode(String typeName, NodeProperties properties){
+		super(typeName, properties);
+		Rectangle2D r = getMinBounds();
+		eShape = new Ellipse2D.Double(0,0,r.getWidth(),r.getHeight());
+	}
+	
+	@Override
+	public ShapeType getShapeType() {
+		return ShapeType.Ellipse;
+	}
+	
+	public static Rectangle2D getOutBounds(Rectangle2D r){
+		double h = r.getHeight()/2;
+		double w = r.getWidth()/2;
+		
+		double anglew = Math.atan(h/w);
+		double angleh = Math.atan(w/h);
+		
+		double a = w + h * Math.tan(angleh)/2;
+		double b = h + w * Math.tan(anglew)/2;
+		
+		return  new Rectangle2D.Double(
+				r.getCenterX() - a,
+				r.getCenterY() - b,
+				2*a,
+				2*b
+				);
+	}
+	
+	@Override
+	protected void reshapeInnerProperties(List<String> insidePropertyTypes){
+		super.reshapeInnerProperties(insidePropertyTypes);
+		eShape.setFrame(super.anyInsideProperties() ? getOutBounds(dataDisplayBounds) : dataDisplayBounds );
+	}
+	
+	@Override
+	public void decode(Document doc, Element nodeTag) throws IOException{
+		super.decode(doc, nodeTag);
+		eShape.setFrame(super.anyInsideProperties() ? getOutBounds(dataDisplayBounds) : dataDisplayBounds );
+	}
+	
+	@Override
+	protected void translateImplementation(Point2D p,double dx, double dy){
+		/* if we clicked on a property node, just move that one */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList)
+				if(pn.contains(p)){
+					pn.translate(dx, dy);
+					return;
+				}
+		eShape.setFrame(eShape.getX() + dx,
+		         eShape.getY() + dy, 
+		         eShape.getWidth(), 
+		         eShape.getHeight());
+		super.translateImplementation(p,dx, dy);
+	}
+	
+	@Override
+	public Rectangle2D getBounds() {
+		return eShape.getBounds2D();
+	}
+	
+	@Override
+	public Point2D getConnectionPoint(Direction d) {
+		return calculateConnectionPoint(d, getBounds());
+	}
+		
+	public static  Point2D calculateConnectionPoint(Direction d, Rectangle2D bounds){
+        double a = bounds.getWidth() / 2;
+        double b = bounds.getHeight() / 2;
+        double x = d.getX();
+        double y = d.getY();
+        double cx = bounds.getCenterX();
+        double cy = bounds.getCenterY();
+
+        if (a != 0 && b != 0 && !(x == 0 && y == 0)){
+            double t = Math.sqrt((x * x) / (a * a) + (y * y) / (b * b));
+            return new Point2D.Double(cx + x / t, cy + y / t);
+        }
+        else{
+            return new Point2D.Double(cx, cy);
+        }
+	}
+
+	@Override
+	public Shape getShape() {
+		return eShape;
+	}
+	
+	@Override
+	public InputStream getSound(){
+		return sound;
+	}
+
+	@Override
+	public Object clone(){
+		EllipticalNode n = (EllipticalNode)super.clone();
+		n.eShape = (Ellipse2D.Double)eShape.clone();
+		return n;
+	}
+	
+	private Ellipse2D.Double eShape;
+	private static InputStream sound;
+	static{
+		sound = EllipticalNode.class.getResourceAsStream("audio/Ellipse.mp3");
+		SoundFactory.getInstance().loadSound(sound);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Model.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,405 @@
+/*  
+ 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.simpletemplate;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+/*
+ * The Wizard Model holds all the data the user enters during the process of creating a diagram template. 
+ * Such data can be re-edited by the user during the same process or if they want to create
+ * a new diagram template out of an existing one. When the user has entered all the data the model is used 
+ * to create an instance of the {@link Diagram class} which is then used as a prototype diagram. 
+ * Diagram instances that the user will edit are created by cloning the relating prototype diagram.  
+ *
+ */
+class Model {
+	Model(){
+		diagramName = new Record();
+		nodes = new ModelMap<Node>();
+		edges = new ModelMap<Edge>();
+	}
+	
+	@Override
+	public String toString(){// FIXME need to port the strings to the .properties file
+		StringBuilder builder = new StringBuilder();
+		builder.append("Diagram Name is ").append(diagramName.value).append(".\n\n");
+		builder.append(diagramName.value + " diagram has "+ (nodes.isEmpty() ? "no" : nodes.size()) + " node"+((nodes.size() == 1) ? "" : "s"));
+		if(!nodes.isEmpty()){
+			builder.append(": ");
+			int i = 0;
+			for(Node n : nodes.values()){
+				builder.append(n.type.value);
+				if(nodes.values().size() == 1)
+					break;
+				if(i == nodes.values().size() - 2){
+					builder.append(" and ");
+				}else if(i < nodes.values().size()-1){
+					builder.append(", ");
+				}
+				i++;
+			}
+		}
+		builder.append(".\n");
+		for(Node n : nodes.values()){
+			builder.append(n.type+" node has a ")
+				.append(n.shape + " shape.\n");
+			builder.append(n.type+" node has "+ (n.properties.isEmpty() ? "no" : n.properties.size())+((n.properties.size() == 1) ? " property" : " properties"));
+			if(!n.properties.isEmpty()){
+				builder.append(": ");
+				int i = 0;
+				for(Property p : n.properties.values()){
+					builder.append(p.type.value);
+					if(n.properties.size() == 1)
+						break;
+					if(i == n.properties.size() - 2){
+						builder.append(" and ");
+					}else if(i < n.properties.size()-1){
+						builder.append(", ");
+					}
+					i++;
+				}
+			}
+			builder.append(".\n");
+			for(Property p : n.properties.values()){
+				builder.append(p.type+" property has position "+p.position);
+				if(p.position.value.equals(SimpleShapeNode.Position.Outside.toString()))
+					builder.append(" and shape "+p.shape+".\n");
+				else
+					builder.append(".\n");
+				builder.append(p.type+" property has "+(p.modifiers.isEmpty() ? "no" : p.modifiers.size())+(p.modifiers.size() == 1 ? " modifier" : " modifiers"));
+				if(!p.modifiers.isEmpty()){
+					builder.append(": ");
+					int i = 0;
+					for(Modifier m : p.modifiers.values()){
+						builder.append(m.type.value);
+						if(p.modifiers.size() == 1)
+							break;
+						if(i == p.modifiers.size() - 2){
+							builder.append(" and ");
+						}else if(i < p.modifiers.size()-1){
+							builder.append(", ");
+						}
+						i++;
+					}
+				}
+				builder.append(".\n");
+				for(Modifier m : p.modifiers.values()){
+					builder.append(m.type+ " modifier ");
+					if(m.format.values.length > 0)
+						builder.append("is formatted as "+m.format+".\n");
+					else
+						builder.append("\n");
+				}
+			}
+		}
+		builder.append('\n');
+		builder.append(diagramName.value + " diagram has "+ (edges.isEmpty() ? "no" : edges.size()) + " edge"+((edges.size() == 1) ? "" : "s"));
+		if(!edges.isEmpty()){
+			builder.append(": ");
+			int i = 0;
+			for(Edge e : edges.values()){
+				builder.append(e.type.value);
+				if(edges.values().size() == 1)
+					break;
+				if(i == edges.values().size() - 2){
+					builder.append(" and ");
+				}else if(i < edges.values().size()-1){
+					builder.append(", ");
+				}
+				i++;
+			}
+		}
+		builder.append(".\n");
+		for(Edge e : edges.values()){
+			builder.append(e.type+ " edge has minimum nodes "+e.minNodes+" and maximum nodes "+e.maxNodes+".\n")
+			 .append(e.type+ " edge has a "+ e.lineStyle+" line style.\n")
+			 .append(e.type+" edge has "+ ((e.arrowHeads.values.length == 0) ? "no harrow heads.\n" : e.arrowHeads.values.length +" harrow heads: "+e.arrowHeads+".\n"));
+		}
+		builder.append('\n');
+		builder.append("Press up and down arrow keys to go through the summary.\n");
+		return builder.toString();
+	}
+
+	Record diagramName;
+	/* these are sets as when we edit an already existing node we just add */   
+	/* to the set the new node and it gets replaced                        */
+	ModelMap<Node> nodes;
+	ModelMap<Edge> edges;
+	
+	static Element copy(Element src, Element dest){
+		if(src instanceof Node){
+			copy((Node)src,(Node)dest);
+		}else if (src instanceof Edge){
+			copy((Edge)src,(Edge)dest);
+		}else if(src instanceof Property){
+			copy((Property)src,(Property)dest);
+		}else{
+			copy((Modifier)src,(Modifier)dest);
+		}
+		return dest;
+	}
+	
+	static void copy(Node src, Node dest){
+		dest.id = src.id;
+		dest.type.value = src.type.value;
+		dest.shape.value = src.shape.value;
+		dest.properties.clear(); 
+		dest.properties.putAll(src.properties);
+	}
+	
+	static void copy(Property src, Property dest){
+		dest.id = src.id;
+		dest.type.value = src.type.value;
+		dest.shape.value = src.shape.value;
+		dest.position.value = src.position.value;
+		dest.modifiers.clear();
+		dest.modifiers.putAll(src.modifiers);
+	}
+	
+	static void copy(Edge src, Edge dest){
+		dest.id = src.id;
+		dest.type.value = src.type.value;
+		dest.lineStyle.value = src.lineStyle.value;
+		dest.maxNodes.value = src.maxNodes.value;
+		dest.minNodes.value = src.minNodes.value;
+		dest.arrowHeads.values = src.arrowHeads.values;
+		dest.arrowHeadsDescriptions.values = src.arrowHeadsDescriptions.values;
+	}
+	
+	static void copy(Modifier src, Modifier dest){
+		dest.id = src.id;
+		dest.type.value = src.type.value;
+		dest.format.values = src.format.values;
+		dest.affix.values = src.affix.values;
+	}
+	
+	static class Record{
+		Record(){
+			value = "";
+		}
+		Record(String value){
+			this.value = value;
+		}
+		@Override
+		public String toString(){
+			return value;
+		}
+		String value;
+	}
+	
+	static class StrArrayRecord{
+		String [] values;
+		StrArrayRecord(){
+			values = new String[0];
+		}
+		
+		StrArrayRecord(String[] values){
+			this.values = values;
+		}
+		
+		@Override
+		public String toString(){
+			StringBuilder builder = new StringBuilder();
+			for(int i=0; i<values.length; i++){
+				builder.append(values[i]);
+				if(values.length == 1)
+					break;
+				if(i == values.length - 2)
+					builder.append(" and ");
+				else if(i < values.length -1 )
+					builder.append(", ");
+			}
+			return builder.toString();
+		}
+	}
+	
+	private static long uniqueId = 0;
+	static class Element {
+		Element(){
+			type = new Record();
+			id = uniqueId++;
+		}
+
+		void clear () {
+			type.value = "";
+			id = uniqueId++;
+		}
+		long id;
+		Record type;
+	}
+	
+	static class Node extends Element{
+		Node(){
+			shape = new Record();	
+			properties = new ModelMap<Property>();
+		}
+		
+		@Override
+		void clear(){
+			super.clear();
+			shape.value = "";
+			properties.clear();
+		}
+		Record shape;
+		ModelMap<Property> properties;
+	}
+	
+	static class Edge extends Element {
+		Edge(){
+			lineStyle = new Record();
+			minNodes = new Record();
+			maxNodes = new Record();
+			arrowHeads = new StrArrayRecord(){
+				@Override
+				public String toString(){
+					StringBuilder builder = new StringBuilder();
+					for(int i=0; i<values.length; i++){
+						builder.append(values[i]+" with label "+arrowHeadsDescriptions.values[i]);
+						if(values.length == 1)
+							break;
+						if(i == values.length - 2)
+							builder.append(" and ");
+						else if(i < values.length -1 )
+							builder.append(", ");
+					}
+					return builder.toString();
+				}
+			};
+			arrowHeadsDescriptions = new StrArrayRecord();
+		}
+		@Override
+		public void clear(){
+			super.clear();
+			lineStyle.value = "";
+			minNodes.value = "";
+			maxNodes.value = "";
+			arrowHeads.values = new String[0];
+			arrowHeadsDescriptions.values = new String[0];
+		}
+		Record lineStyle;
+		Record minNodes;
+		Record maxNodes;
+		StrArrayRecord arrowHeads;
+		StrArrayRecord arrowHeadsDescriptions;
+	}
+	
+	static class Property extends Element{
+		Property(){
+			position = new Model.Record();
+			shape = new Model.Record();
+			modifiers = new ModelMap<Modifier>();
+		}
+		Record position;
+		Record shape;
+		ModelMap<Modifier> modifiers;
+		
+		@Override
+		void clear(){
+			super.clear();
+			modifiers.clear();
+			position.value = "";
+			shape.value = "";
+		}
+	}
+	
+	static class Modifier extends Element {
+		StrArrayRecord format;
+		StrArrayRecord affix;
+		
+		Modifier(){
+			affix = new StrArrayRecord();
+			format = new StrArrayRecord(){
+				@Override
+				public String toString(){
+					ResourceBundle resources = ResourceBundle.getBundle(SpeechWizardDialog.class.getName());
+					StringBuilder builder = new StringBuilder();
+					for(int i=0; i<values.length; i++){
+						builder.append(values[i]);
+						if(values[i].equals(resources.getString("modifier.format.prefix")) && !affix.values[0].isEmpty()){
+							builder.append(' ').append(affix.values[0]);
+						}
+						if(values[i].equals(resources.getString("modifier.format.suffix")) && !affix.values[1].isEmpty()){
+							builder.append(' ').append(affix.values[1]);
+						}
+
+						if(values.length == 1)
+							break;
+						if(i == values.length - 2)
+							builder.append(" and ");
+						else if(i < values.length -1 )
+							builder.append(", ");
+					}
+					return builder.toString();
+				}
+			};
+				
+			/* affix is always length = 2 as it only contains prefix and suffix string */
+			/* eventually it contains empty strings but its length is not variable     */
+			affix.values = new String[2];
+		}
+		
+		@Override
+		void clear(){
+			super.clear();
+			format.values = new String[0];
+			affix.values =  new String[2];
+		}
+	}
+}
+
+
+@SuppressWarnings("serial")
+class ModelMap<T extends Model.Element> extends LinkedHashMap<Long,T> {
+	public ModelMap(){
+		namesMap = new LinkedHashMap<Long,String>();
+	}
+	
+	@Override
+	public void clear(){
+		namesMap.clear();
+		super.clear();
+	}
+	
+	public T put(Long key, T value){
+		namesMap.put(key, value.type.value);
+		return super.put(key, value);
+	}
+	
+	public void putAll(Map<? extends Long,? extends T> m){
+		for(Map.Entry<? extends Long,? extends T> entry : m.entrySet()){
+			namesMap.put(entry.getKey(), entry.getValue().type.value);
+		}
+		super.putAll(m);
+	}
+	
+	public T remove(Object key){
+		namesMap.remove(key);
+		return super.remove(key);
+	}
+	
+	public Collection<String> getNames(){
+		return namesMap.values();
+	}
+	
+	private Map<Long,String> namesMap;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/ModifierView.java	Fri Dec 16 17:35:51 2011 +0000
@@ -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.simpletemplate;
+
+/**
+ * This immutable class represents the view associated with the modifier type
+ * associated with it in a {@link NodeProperties} instance. 
+ * 
+ * */
+public class ModifierView {
+	
+	public ModifierView(boolean underline, boolean bold, boolean italic,
+			String prefix, String postfix) {
+		this.underline = underline;
+		this.bold = bold;
+		this.italic = italic;
+		this.prefix = prefix;
+		this.suffix = postfix;
+	}
+	
+	/* Getters */
+	public boolean isUnderline() {
+		return underline;
+	}
+	
+	public boolean isBold() {
+		return bold;
+	}
+	
+	public boolean isItalic() {
+		return italic;
+	}
+	
+	public String getPrefix() {
+		return prefix;
+	}
+	
+	public String getSuffix() {
+		return suffix;
+	}
+
+	@Override
+	public String toString(){
+		StringBuilder builder = new StringBuilder("ModifierView: ");
+		builder.append("bold ").append(bold).append("; ");
+		builder.append("italic ").append(italic).append("; ");
+		builder.append("underline ").append(underline).append("; ");
+		builder.append("prefix ").append(prefix).append("; ");
+		builder.append("suffix ").append(suffix).append("; ");
+		return builder.toString();
+	}
+	
+	private boolean underline;
+	private boolean bold;
+	private boolean italic;
+	private String prefix;
+	private String suffix;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/MultiLineString.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,267 @@
+/*  
+ 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/)
+
+ 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.simpletemplate;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JLabel;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
+
+/**
+   A string that can extend over multiple lines.
+*/
+public class MultiLineString {
+   /**
+      Constructs an empty, centered, normal size multiline
+      string.
+   */
+   public MultiLineString(){ 
+      text = ""; 
+      arrayText = null;
+      justification = CENTER;
+      size = NORMAL;
+      isSingleLine = true;
+   }
+   /**
+      Sets the value of the text property.
+      @param newValue the text of the multiline string
+   */
+   public void setText(String newValue){
+	   setText(newValue,null);
+   }
+   
+   public void setText(String newValue, ModifierView[] modifierViews) {
+	   isSingleLine = true;
+	   text = newValue; 
+	   this.modifierViews = modifierViews;
+	   format(); 
+   }
+   
+   public void setText(String[] newValue, Modifiers modifiers){
+	   isSingleLine = false;
+	   this.modifiers = modifiers;
+	   arrayText = newValue;
+	   format();
+   }
+   
+   public void setBold(boolean bold){
+	   isBold = bold;
+	   format();
+   }
+   /**
+      Gets the value of the text property.
+      @return the text of the multiline string
+    */
+   public String getText() { 
+	   if(isSingleLine)
+		   return text;
+	   else{
+		   StringBuilder builder = new StringBuilder("");
+		   for(int i=0; i<arrayText.length; i++){
+			   builder.append(arrayText[i]);
+			   builder.append('\n');
+		   }
+		   return builder.toString();
+	   }
+   }
+   /**
+      Sets the value of the justification property.
+      @param newValue the justification, one of LEFT, CENTER, 
+      RIGHT
+   */
+   public void setJustification(int newValue) { justification = newValue; format(); }
+   /**
+      Gets the value of the justification property.
+      @return the justification, one of LEFT, CENTER, 
+      RIGHT
+   */
+   public int getJustification() { return justification; }
+   /**
+      Sets the value of the size property.
+      @param newValue the size, one of SMALL, NORMAL, LARGE
+   */
+   public void setSize(int newValue) { size = newValue; format(); }
+   /**
+      Gets the value of the size property.
+      @return the size, one of SMALL, NORMAL, LARGE
+   */
+   public int getSize() { return size; }
+   
+   @Override
+   public String toString(){
+	   if(isSingleLine)
+		   return text.replace('\n', '|');
+	   else 
+		   return getText().replace('\n', '|');
+   }
+
+   private void format(){
+      StringBuffer prefix = new StringBuffer();
+      StringBuffer suffix = new StringBuffer();
+      StringBuffer htmlText = new StringBuffer();
+      prefix.append("&nbsp;");
+      suffix.insert(0, "&nbsp;");
+      if (size == LARGE){
+         prefix.append("<font size=\"+1\">");
+         suffix.insert(0, "</font>");
+      }
+      if (size == SMALL){
+         prefix.append("<font size=\"-1\">");
+         suffix.insert(0, "</font>");
+      }
+      
+      htmlText.append("<html>");
+      if(isSingleLine){
+    	  if(isBold)
+    		  prefix.append("<b>");
+    	  htmlText.append(prefix);
+    	  
+    	  String formattedText = text;
+    	  if(modifierViews != null){
+	    	  for(ModifierView view : modifierViews){
+	    		  formattedText = formatFromView(formattedText,view);
+	    	  }
+    	  }
+    	  htmlText.append(formattedText);
+    	  
+    	  if(isBold)
+    		  suffix.insert(0, "</b>");
+    	  htmlText.append(suffix);
+      }else{ // multi line
+    	  boolean first = true;
+    	  for(int i=0; i<arrayText.length; i++){
+    		  if (first) 
+    			  first = false; 
+    		  else 
+    			  htmlText.append("<br>");
+    		  htmlText.append(prefix);
+    		  String textLine = arrayText[i];
+    		  Set<Integer> indexes = modifiers.getIndexes(i);
+    		  for(Integer I : indexes){
+    			  ModifierView view = (ModifierView)modifiers.getView(modifiers.getTypes().get(I));
+    			  textLine = formatFromView(textLine, view);
+    		  }
+    		  htmlText.append(textLine);
+    		  htmlText.append(suffix);
+    	  }
+      }
+      htmlText.append("</html>");
+
+      // replace any < that are not followed by {u, i, b, tt, font, br} with &lt;      
+      List<String> dontReplace = Arrays.asList(new String[] { "u", "i", "b", "tt", "font", "br" });
+      
+      int ltpos = 0;
+      while (ltpos != -1){
+         ltpos = htmlText.indexOf("<", ltpos + 1);
+         if (ltpos != -1 && !(ltpos + 1 < htmlText.length() && htmlText.charAt(ltpos + 1) == '/')){
+            int end = ltpos + 1;
+            while (end < htmlText.length() && Character.isLetter(htmlText.charAt(end))) end++;
+            if (!dontReplace.contains(htmlText.substring(ltpos + 1, end)))
+               htmlText.replace(ltpos, ltpos+1, "&lt;");
+         }
+      }
+            
+      label.setText(htmlText.toString());
+      if (justification == LEFT) label.setHorizontalAlignment(JLabel.LEFT);
+      else if (justification == CENTER) label.setHorizontalAlignment(JLabel.CENTER);
+      else if (justification == RIGHT) label.setHorizontalAlignment(JLabel.RIGHT);
+   }
+   
+   private String formatFromView(String text, ModifierView view){
+	   if(view == null)
+		   return text;
+	   
+	   StringBuilder prefix = new StringBuilder("");
+	   StringBuilder suffix = new StringBuilder("");
+	   
+	   prefix.append(view.getPrefix());
+	   suffix.append(view.getSuffix());
+	   
+	   if(view.isBold()){
+		   prefix.insert(0,"<b>");
+		   suffix.append("</b>");
+	   }
+	   
+	   if(view.isItalic()){
+			   prefix.insert(0,"<i>");
+			   suffix.append("</i>");
+	   }
+	   
+	   if(view.isUnderline()){
+		   prefix.insert(0,"<u>");
+		   suffix.append("</u>");
+	   }
+	   return prefix.append(text).append(suffix).toString();
+   }
+   
+    /**
+      Gets the bounding rectangle for this multiline string.
+      @param g2 the graphics context
+      @return the bounding rectangle (with top left corner (0,0))
+   */
+   public Rectangle2D getBounds(){
+	  if(isSingleLine){ 
+		  if (text.length() == 0) 
+			  return new Rectangle2D.Double();
+	  }else {
+		  if(arrayText.length == 0)
+			  return new Rectangle2D.Double();
+	  }
+      Dimension dim = label.getPreferredSize();       
+      return new Rectangle2D.Double(0, 0, dim.getWidth(), dim.getHeight());
+   }
+
+   /**
+      Draws this multiline string inside a given rectangle
+      @param g2 the graphics context
+      @param r the rectangle into which to place this multiline string
+   */
+   public void draw(Graphics2D g2, Rectangle2D r){
+      label.setFont(g2.getFont());
+      label.setBounds(0, 0, (int) r.getWidth(), (int) r.getHeight());
+      g2.translate(r.getX(), r.getY());
+      label.paint(g2);
+      g2.translate(-r.getX(), -r.getY());        
+   }
+
+   public static final int LEFT = 0;
+   public static final int CENTER = 1;
+   public static final int RIGHT = 2;
+   public static final int LARGE = 3;
+   public static final int NORMAL = 4;
+   public static final int SMALL = 5;
+
+   private String text;
+   private String[] arrayText;
+   private Modifiers modifiers;
+   private ModifierView[] modifierViews;
+   private int justification;
+   private int size;
+   private boolean isBold;
+   private transient JLabel label = new JLabel();
+   private boolean isSingleLine;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/PropertyView.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,48 @@
+/*  
+ 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.simpletemplate;
+
+import uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleShapeNode.Position;
+import uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleShapeNode.ShapeType;
+
+/**
+ * This immutable class represents the view associated with the property type 
+ * associated with it in a {@link NodeProperties} instance. 
+ * 
+ * */
+public class PropertyView {
+	public PropertyView(Position position, ShapeType shapeType) {
+		super();
+		this.position = position;
+		this.shapeType = shapeType;
+	}
+	
+	/* Getters */
+	public SimpleShapeNode.Position getPosition() {
+		return position;
+	}
+	
+	public SimpleShapeNode.ShapeType getShapeType() {
+		return shapeType;
+	}
+
+	private SimpleShapeNode.Position position;
+	private SimpleShapeNode.ShapeType shapeType;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/RectangularNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,143 @@
+/*  
+ 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/)
+
+ 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.simpletemplate;
+
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.gui.Direction;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+
+/**
+   A rectangular shaped diagram node.
+*/
+@SuppressWarnings("serial")
+public class RectangularNode extends SimpleShapeNode{
+	
+	public RectangularNode(String nodeType, NodeProperties properties){
+		super(nodeType, properties);
+		bounds = dataDisplayBounds;
+	}
+	
+	@Override
+	public ShapeType getShapeType() {
+		return ShapeType.Rectangle;
+	}
+	
+	@Override
+	protected void translateImplementation(Point2D p, double dx, double dy){
+		/* if we clicked on a property node, just move that one */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList)
+				if(pn.contains(p)){
+					pn.translate(dx, dy);
+					return;
+				}
+		
+		bounds.setFrame(bounds.getX() + dx,
+			bounds.getY() + dy, 
+			bounds.getWidth(), 
+			bounds.getHeight());
+		super.translateImplementation(p, dx, dy);
+	}
+
+	@Override
+	protected void reshapeInnerProperties(List<String> insidePropertyTypes){
+		super.reshapeInnerProperties(insidePropertyTypes);
+		bounds.setFrame(dataDisplayBounds);
+	}
+	
+	@Override
+	public void decode(Document doc, Element nodeTag) throws IOException{
+		super.decode(doc, nodeTag);
+		bounds.setFrame(dataDisplayBounds);
+	}
+	
+	@Override
+	public Rectangle2D getBounds(){
+		return (Rectangle2D)bounds.clone();
+	}
+
+	@Override
+	public Point2D getConnectionPoint(Direction d){
+		return calculateConnectionPoint(d, getBounds());
+	}
+	
+	public static  Point2D calculateConnectionPoint(Direction d, Rectangle2D bounds){
+		double slope = bounds.getHeight() / bounds.getWidth();
+		double ex = d.getX();
+		double ey = d.getY();
+		double x = bounds.getCenterX();
+		double y = bounds.getCenterY();
+      
+		if (ex != 0 && -slope <= ey / ex && ey / ex <= slope){  
+			// intersects at left or right boundary
+			if (ex > 0){
+				x = bounds.getMaxX();
+				y += (bounds.getWidth() / 2) * ey / ex;
+			}else{
+				x = bounds.getX();
+				y -= (bounds.getWidth() / 2) * ey / ex;
+			}
+		}else if (ey != 0){  
+			// intersects at top or bottom
+			if (ey > 0){
+				x += (bounds.getHeight() / 2) * ex / ey;
+				y = bounds.getMaxY();
+			}else{
+				x -= (bounds.getHeight() / 2) * ex / ey;
+				y = bounds.getY();
+			}
+		}
+		return new Point2D.Double(x, y);
+	}
+
+	public Shape getShape(){
+		return bounds;
+	}
+   
+	@Override
+	public InputStream getSound(){
+		return sound;
+	}
+	
+	public Object clone(){
+		RectangularNode cloned = (RectangularNode)super.clone();
+		cloned.bounds = (Rectangle2D.Double)bounds.clone();
+		return cloned;
+	}	
+   
+	private Rectangle2D.Double bounds;
+	private static InputStream sound;
+	
+	static{
+		sound = RectangularNode.class.getResourceAsStream("audio/Rectangle.mp3");
+		SoundFactory.getInstance().loadSound(sound);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeEdge.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,321 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Graphics2D;
+import java.awt.Stroke;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.GraphElement;
+import uk.ac.qmul.eecs.ccmi.gui.LineStyle;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+
+@SuppressWarnings("serial")
+public class SimpleShapeEdge extends Edge {
+
+	public SimpleShapeEdge(String type, LineStyle style, ArrowHead[] heads, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes) {
+		super(type,availableEndDescriptions,minAttachedNodes,maxAttachedNodes,style);
+		this.heads = heads;
+		currentHeads = new HashMap<Node,ArrowHead>();
+	}
+	
+	@Override
+	public boolean removeNode(DiagramNode n){
+		currentHeads.remove(n);
+		return super.removeNode(n);
+	}
+
+	@Override
+	public void draw(Graphics2D g2) {
+		 Stroke oldStroke = g2.getStroke();
+		 g2.setStroke(getStyle().getStroke());
+		
+		 /* straight line  */
+		 if(points.isEmpty()){
+			 Line2D line = getSegment(getNodeAt(0),getNodeAt(1));
+			 g2.draw(line);
+			 
+			 /* draw arrow heads if any */
+			 ArrowHead h = currentHeads.get(getNodeAt(0));
+			 if( h != null && h != ArrowHead.TAIL){
+				 Line2D revLine = getSegment(getNodeAt(1),getNodeAt(0));
+				 h.draw(g2, revLine.getP1(), revLine.getP2());
+			 }
+			 h = currentHeads.get(getNodeAt(1));
+			 if( h != null && h != ArrowHead.TAIL){
+				 h.draw(g2, line.getP1(), line.getP2());
+			 }
+			 
+			 /* draw labels if any */
+			 String label;
+			 if((label = getEndLabel(getNodeAt(0))) != null){
+				 EdgeDrawSupport.drawString(g2, line.getP2(), line.getP1(), currentHeads.get(getNodeAt(0)), label, false);
+			 }
+			 if((label = getEndLabel(getNodeAt(1))) != null){
+				 EdgeDrawSupport.drawString(g2, line.getP1(), line.getP2(), currentHeads.get(getNodeAt(1)), label, false);
+			 }
+			 
+			 /* draw name if any */
+			 if(!getName().isEmpty()){
+				 EdgeDrawSupport.drawString(g2, line.getP2(), line.getP1(), null, getName(), true);
+			 }
+			 if(!"".equals(getNotes()))
+				 EdgeDrawSupport.drawMarker(g2,line.getP1(),line.getP2());
+		 }else{
+			 /* edge with inner points: it can be a multiended(eventually bended) edge or a straight bended edge */
+			 
+			 /* arrow and labels are drawn in the same way in either case */
+			 for(InnerPoint p : points){
+				 for(GraphElement ge : p.getNeighbours()){
+					 g2.draw(getSegment(p,ge));
+					 if(ge instanceof Node){ // this is the inner point which is connected to a Node 
+						/* draw arrow if any */
+						ArrowHead h = currentHeads.get((Node)ge);
+						if(h != null && h != ArrowHead.TAIL){
+							Line2D line = getSegment(p,ge);
+							h.draw(g2, line.getP1() , line.getP2());
+						}
+						
+						/* draw label if any */
+						String label = getEndLabel((Node)ge);
+						if(label != null){
+							Line2D line = getSegment(p,ge);
+							EdgeDrawSupport.drawString(g2, line.getP1(), line.getP2(), currentHeads.get((Node)ge), label, false);
+						}
+					 }
+				 }
+				 p.draw(g2);
+			 }
+			 /* name is drawn differently : 
+			  * for multiended edges name is drawn on the master inner point
+			  * for two ends bended name is drawn in (about) the middle point of the edge 
+			  */
+			 
+			 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());
+				 if(!getName().isEmpty())
+					 EdgeDrawSupport.drawString(g2, p, q, null, getName(), true);
+				 if(!"".equals(getNotes()))
+					 EdgeDrawSupport.drawMarker(g2,p,q);
+			 }else{
+			 /* straight edge which has been bended */ 
+				 GraphElement ge1 = getNodeAt(0);
+				 GraphElement ge2 = getNodeAt(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;
+					 }
+				 }
+				 
+				 /* draw name if any */
+				 if(!getName().isEmpty()){
+					 /* 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());
+						 
+					 }
+					 if(!getName().isEmpty())
+						 EdgeDrawSupport.drawString(g2, p, q, null, getName(), true);
+					 if(!"".equals(getNotes()))
+						 EdgeDrawSupport.drawMarker(g2,p,q);
+				 }
+			 }
+		 }
+		 g2.setStroke(oldStroke);
+	}
+	
+	public Rectangle2D getBounds() {
+		if(points.isEmpty()){
+			return getSegment(getNodeAt(0), getNodeAt(1)).getBounds2D();
+		}else{
+			Rectangle2D bounds = points.get(0).getBounds();
+			for(InnerPoint p : points){
+				for(GraphElement ge : p.getNeighbours())
+					bounds.add(getSegment(p,ge).getBounds2D());
+			}
+			return bounds;
+		}
+	}
+	
+	public ArrowHead[] getHeads() {
+		return heads;
+	}
+	
+	@Override
+	public InputStream getSound(){
+		switch(getStyle()){
+		case Dashed : return dashedSound;
+		case Dotted : return dottedSound;
+		default : return straightSound;
+		}
+	}
+	
+	@Override
+	 public void setEndDescription(DiagramNode diagramNode, int index){
+		Node n = (Node)diagramNode;
+		if(index == NO_END_DESCRIPTION_INDEX){
+			 currentHeads.remove(n);
+			 super.setEndDescription(n, index);
+		 }else{
+			 ArrowHead h = heads[index];
+			 currentHeads.put(n, h);
+			 super.setEndDescription(n, index);
+		 }
+	}
+	
+	@Override
+	public void encode(Document doc, Element parent, List<Node> nodes){
+		super.encode(doc, parent, nodes);
+		/* add the head attribute to the NODE tag */
+		NodeList nodeTagList = parent.getElementsByTagName(PersistenceManager.NODE);
+		for(int i = 0 ; i< nodeTagList.getLength(); i++){
+			Element nodeTag = (Element)nodeTagList.item(i);
+			String nodeIdAsString = nodeTag.getAttribute(PersistenceManager.ID);
+			long nodeId = Long.parseLong(nodeIdAsString);
+			Node node = null;
+			/* find the node with the id of the tag */
+			for(Node n : nodes)
+				if(n.getId() == nodeId){
+					node = n;
+					break;
+				}
+			String head = (currentHeads.get(node) == null) ? "" : currentHeads.get(node).toString();
+			nodeTag.setAttribute(SimpleShapePrototypePersistenceDelegate.HEAD, head );
+		}
+	}
+	
+	@Override
+	public void decode(Document doc, Element edgeTag, Map<String,Node> nodesId) throws IOException{
+		super.decode(doc, edgeTag, nodesId);
+		NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE);
+		for(int i=0; i<nodeList.getLength(); i++){
+			Element nodeTag = (Element)nodeList.item(i);
+			String id = nodeTag.getAttribute(PersistenceManager.ID);
+			if(id.isEmpty()) 
+				throw new IOException();
+			String head = nodeTag.getAttribute(SimpleShapePrototypePersistenceDelegate.HEAD);
+			if(!head.isEmpty()){
+				ArrowHead headShape = null;
+				try{
+					headShape = ArrowHead.getArrowHeadFromString(head);
+				}catch(IOException e){
+					throw e;
+				}
+				currentHeads.put(nodesId.get(id), headShape);
+				int headDescriptionIndex = Edge.NO_END_DESCRIPTION_INDEX;
+				for(int j=0; j<heads.length;j++){
+					if(heads[j].equals(headShape)){
+						headDescriptionIndex = j;
+						break;
+					}
+				}
+				setEndDescription(nodesId.get(id),headDescriptionIndex);
+			}
+		}
+	}
+	
+	@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() );
+	}
+	
+	
+
+	private ArrowHead[] heads;	
+	private Map<Node,ArrowHead> currentHeads;
+	private static InputStream straightSound;
+	private static InputStream dottedSound;
+	private static InputStream dashedSound;
+	
+	static{
+		Class<SimpleShapeEdge> c = SimpleShapeEdge.class; 
+		straightSound = c.getResourceAsStream("audio/straightLine.mp3");
+		dottedSound = c.getResourceAsStream("audio/dashedLine.mp3");
+		dashedSound = c.getResourceAsStream("audio/dottedLine.mp3");
+		SoundFactory.getInstance().loadSound(straightSound);
+		SoundFactory.getInstance().loadSound(dottedSound);
+		SoundFactory.getInstance().loadSound(dashedSound);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,584 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.gui.Direction;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+import uk.ac.qmul.eecs.ccmi.utils.Pair;
+
+/**
+ * 
+ * A diagram node that can be represented visually as a simple shape such as 
+ * a rectangle, square, circle, ellipse or triangle.
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class SimpleShapeNode extends Node {
+	
+	public static SimpleShapeNode getInstance(ShapeType shapeType, String typeName, NodeProperties properties){
+		switch(shapeType){
+		case Rectangle :
+			return new RectangularNode(typeName, properties);
+		case Square : 
+			return new SquareNode(typeName, properties);
+		case Circle :
+			return new CircleNode(typeName, properties);
+		case Ellipse :
+			return new EllipticalNode(typeName, properties);
+		case Triangle :
+			return new TriangularNode(typeName, properties);
+		}
+		return null;
+	}
+	
+	protected SimpleShapeNode(String typeName, NodeProperties properties){
+		super(typeName, properties);
+		dataDisplayBounds = (Rectangle2D.Double)getMinBounds();
+		/* Initialise the data structures for displaying the properties inside and outside */
+		propertyNodesMap = new LinkedHashMap<String,List<PropertyNode>>(); 
+		int numInsideProperties = 0;
+		for(String type : getProperties().getTypes()){
+			if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside)
+				numInsideProperties++;
+			else
+				propertyNodesMap.put(type, new LinkedList<PropertyNode>());
+		}
+		propertyLabels = new MultiLineString[numInsideProperties];
+		nameLabel = new MultiLineString();
+	}
+	
+	@Override
+	protected void notifyChange(ElementChangedEvent evt){
+		if(!evt.getChangeType().equals("translate")&&!evt.getChangeType().equals("stop_move")) //don't reshape for just moving 
+			reshape();  
+		super.notifyChange(evt);
+	}
+	
+	@Override
+	public void setId(long id){
+		super.setId(id);
+		/* when they are given an id nodes change name into "new <type> node <id>" *
+		 * where <type> is the actual type of the node and <id> is the given id    *
+		 * therefore a reshape is necessary to display the new name                */
+		Rectangle2D boundsBeforeReshape = getBounds();
+		reshape();
+		/* the reshape might change the bounds, so the shape is translated so that the top-left  *
+		 * point is at the same position as before just to keep it more consistent               */
+		Rectangle2D boundsAfterReshape = getBounds();
+		translateImplementation(
+				new Point2D.Double(),
+				boundsBeforeReshape.getX() - boundsAfterReshape.getX(),
+				boundsBeforeReshape.getY() - boundsAfterReshape.getY()
+			);
+	}
+	
+	@Override
+	public boolean contains(Point2D p) {
+		if (getShape().contains(p))
+			return true;
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList) 
+				if(pn.contains(p))
+					return true;
+		return false;
+	}
+	
+	protected void reshape(){
+		Pair<List<String>, List<String>> splitPropertyTypes = splitPropertyTypes();
+		/* properties displayed internally */
+		reshapeInnerProperties(splitPropertyTypes.first);
+		/* properties displayed externally */ 
+		reshapeOuterProperties(splitPropertyTypes.second);	
+	}
+	
+	protected Pair<List<String>, List<String>> splitPropertyTypes(){
+		List<String> types = getProperties().getTypes();
+		ArrayList<String> insidePropertyTypes = new ArrayList<String>(types.size());
+		ArrayList<String> outsidePropertyTypes = new ArrayList<String>(types.size());
+		for(String type : types){
+			if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside)
+				insidePropertyTypes.add(type);
+			else
+				outsidePropertyTypes.add(type);
+		}
+		
+		return new Pair<List<String>, List<String>> (insidePropertyTypes,outsidePropertyTypes);
+	}
+	
+	protected void reshapeOuterProperties(List<String> outsidePropertyTypes){
+		for(String type : outsidePropertyTypes){
+			List<PropertyNode> propertyNodes = propertyNodesMap.get(type);
+			List<String> propertyValues = getProperties().getValues(type);
+			int diff = propertyNodes.size()-propertyValues.size();
+			if(diff > 0) // properties have been removed
+				for(int i=0; i < diff; i++)
+					propertyNodes.remove(propertyNodes.size() - 1);
+			else if(diff < 0){ // properties have been added. We need more properties node.
+				for(int i=0; i < -diff; i++){
+					PropertyNode propertyNode = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType());
+					Rectangle2D bounds = getBounds();
+					double x = bounds.getCenterX() - bounds.getWidth()/2 - PROP_NODE_DIST;
+					double y = bounds.getCenterX() - PROP_NODE_DIST * i;
+					propertyNode.translate(x, y);
+					propertyNodes.add(propertyNode);
+				}
+			}
+			/* set the text on all the property nodes */
+			int i = 0;
+			for(String text : propertyValues){
+				NodeProperties.Modifiers modifiers = getProperties().getModifiers(type);
+				Set<Integer> viewIndexes = modifiers.getIndexes(i);
+				ModifierView[] views = new ModifierView[viewIndexes.size()]; 
+				int j =0;
+				for(Integer I : viewIndexes){
+					views[j] = (ModifierView) getProperties().getModifiers(type).getView(modifiers.getTypes().get(I));
+					j++;
+				}
+				propertyNodes.get(i).setText(text,views);
+				i++;
+			}
+		}
+	}
+	
+	protected void reshapeInnerProperties(List<String> insidePropertyTypes){
+		/* set the bounds for each multiline string and the resulting bound of the node */
+		nameLabel = new MultiLineString();
+		nameLabel.setText(getName().isEmpty() ? " " : getName());
+		nameLabel.setBold(true);		
+		Rectangle2D r = nameLabel.getBounds();
+		
+		for(int i=0; i<insidePropertyTypes.size();i++){
+			propertyLabels[i] = new MultiLineString();
+			String propertyType = insidePropertyTypes.get(i);
+	    	if(getProperties().getValues(propertyType).size() == 0){
+	    		propertyLabels[i].setText(" ");
+	    	}else{
+	    		propertyLabels[i].setJustification(MultiLineString.LEFT);
+	    		String[] array = new String[getProperties().getValues(propertyType).size()];
+	    		propertyLabels[i].setText(getProperties().getValues(propertyType).toArray(array), getProperties().getModifiers(propertyType));
+	    	}
+	    	r.add(new Rectangle2D.Double(r.getX(),r.getMaxY(),propertyLabels[i].getBounds().getWidth(),propertyLabels[i].getBounds().getHeight()));
+    	}
+		/* set a gap to uniformly distribute the extra space among property rectangles to reach the minimum bound's height */
+		boundsGap = 0;
+		Rectangle2D.Double minBounds = (Rectangle2D.Double)getMinBounds();
+		if(r.getHeight() < minBounds.height){
+			boundsGap = minBounds.height - r.getHeight();
+			boundsGap /= insidePropertyTypes.size();
+		}
+		r.add(minBounds); //make sure it's at least as big as the minimum bounds 
+		dataDisplayBounds.setFrame(new Rectangle2D.Double(dataDisplayBounds.x,dataDisplayBounds.y,r.getWidth(),r.getHeight()));
+	}
+	
+	/**
+	  Draw the node from the Shape with shadow
+	  @param g2 the graphics context
+	*/
+	@Override
+	public void draw(Graphics2D g2){
+		/* draw the external shape */
+		Shape shape = getShape();
+			if (shape == null) return;
+		Color oldColor = g2.getColor();
+		g2.translate(SHADOW_GAP, SHADOW_GAP);      
+		g2.setColor(SHADOW_COLOR);
+		g2.fill(shape);
+		g2.translate(-SHADOW_GAP, -SHADOW_GAP);
+		g2.setColor(g2.getBackground());
+		g2.fill(shape);
+		g2.setColor(Color.BLACK);
+		g2.draw(shape);      
+		g2.setColor(oldColor);
+	
+		/* if there ain't any property to display inside, then display the name in the middle of the data Display bounds */
+		if(!anyInsideProperties()){
+	    	nameLabel.draw(g2, dataDisplayBounds);
+		}else{
+	    	/* draw name */
+	    	Rectangle2D currentBounds = new Rectangle2D.Double(
+	    			dataDisplayBounds.x,
+	    			dataDisplayBounds.y,
+	    			dataDisplayBounds.getWidth(),
+	    			nameLabel.getBounds().getHeight());
+	    	if(drawPropertySeparators){
+	    		Shape oldClip = g2.getClip();
+	    		g2.setClip(getShape());
+	    		g2.draw(new Rectangle2D.Double(
+	    				getBounds().getX(),
+		    			dataDisplayBounds.y,
+		    			getBounds().getWidth(),
+		    			nameLabel.getBounds().getHeight())
+	    				);
+	    		g2.setClip(oldClip);
+	    	}
+	    	nameLabel.draw(g2, currentBounds);
+	    	
+	    	/* draw internal properties */
+	    	Rectangle2D previousBounds;
+	    	for(int i=0;i<propertyLabels.length;i++){
+	    		previousBounds = currentBounds;
+	    		currentBounds = new Rectangle2D.Double(
+	    				previousBounds.getX(),
+	    				previousBounds.getMaxY(),
+	    				dataDisplayBounds.getWidth(),
+	    				propertyLabels[i].getBounds().getHeight()+boundsGap);
+	    		if(drawPropertySeparators){
+		    		Shape oldClip = g2.getClip();
+		    		g2.setClip(getShape());
+	    			g2.draw(new Rectangle2D.Double(
+		    				getBounds().getX(),
+			    			currentBounds.getY(),
+			    			getBounds().getWidth(),
+			    			currentBounds.getHeight())
+		    				);
+	    			g2.setClip(oldClip);
+	    		}
+	    		propertyLabels[i].draw(g2, currentBounds);
+	    	}
+	    }
+	    	
+		/* draw external properties */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList){
+				pn.draw(g2);
+				Direction d = new Direction( getBounds().getCenterX() - pn.getCenter().getX(), getBounds().getCenterY() - pn.getCenter().getY());
+				g2.draw(new Line2D.Double(pn.getConnectionPoint(d), getConnectionPoint(d.turn(180))));
+			}
+		/* draw visual cue for bookmarks and notes */
+		super.draw(g2);
+	}
+	
+	protected Rectangle2D getMinBounds(){
+		return (Rectangle2D)minBounds.clone();
+	}
+	
+	public abstract  ShapeType getShapeType();
+
+	@Override
+	public void encode(Document doc, Element parent){
+		super.encode(doc, parent);
+		if(getProperties().isEmpty())
+			 return;
+		NodeList propTagList = doc.getElementsByTagName(PersistenceManager.PROPERTY);
+		
+		/* scan all the PROPERTY tags to add the position tag */
+		for(int i = 0 ; i< propTagList.getLength(); i++){
+			Element propertyTag = (Element)propTagList.item(i);
+			Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+			String type = typeTag.getTextContent();
+			
+			/* a property of another node, continue */
+			if(!getProperties().getTypes().contains(type))
+				continue;
+			
+			if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
+				continue;
+			
+			List<String> values = getProperties().getValues(type);
+			if(values.isEmpty())
+				continue;
+			
+			NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
+			List<PropertyNode> pnList = propertyNodesMap.get(type);
+			for(int j=0; j<elementTagList.getLength();j++){
+				Element elementTag = (Element)elementTagList.item(j);
+				Element positionTag = doc.createElement(SimpleShapePrototypePersistenceDelegate.POSITION);	
+				positionTag.setAttribute(PersistenceManager.X, String.valueOf(pnList.get(j).getX()));
+				positionTag.setAttribute(PersistenceManager.Y, String.valueOf(pnList.get(j).getY()));
+				elementTag.appendChild(positionTag);
+			}
+		}
+	}
+	
+	@Override
+	public void decode(Document doc, Element nodeTag) throws IOException{
+		super.decode(doc, nodeTag);
+		
+		NodeList propTagList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
+
+		/* split the property types into internal and external, properties have been set by super.decodeXMLInstance  */
+		ArrayList<String> insidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size());
+		ArrayList<String> outsidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size());
+		for(String type : getProperties().getTypes()){
+			if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
+				insidePropertyTypes.add(type);
+			else
+				outsidePropertyTypes.add(type);
+		}
+		
+		/* set the multi-line string bounds for the properties which are displayed internally */		
+		reshapeInnerProperties(insidePropertyTypes);
+		
+		/* scan all the PROPERTY tags to decode the position tag of the properties which are displayed externally */
+		for(int i = 0 ; i< propTagList.getLength(); i++){
+			Element propertyTag = (Element)propTagList.item(i);
+			if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
+				throw new IOException();
+			Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+
+			String type = typeTag.getTextContent();
+			/* (check on whether type exists in the node type definition is done in super.decode */
+			
+			if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
+				continue;
+			/* this will create external nodes and assign them their position */
+			NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
+			List<PropertyNode> pnList = new LinkedList<PropertyNode>();
+			for(int j=0; j<elementTagList.getLength();j++){
+				Element elementTag  = (Element)elementTagList.item(j);
+				if(elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0) == null ||
+						elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
+					throw new IOException();
+				Element positionTag = (Element)elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0);				
+				Element valueTag = (Element)elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
+				double dx,dy;
+				try{
+					dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
+					dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
+				}catch(NumberFormatException nfe){
+					throw new IOException();
+				}
+				PropertyNode pn = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType());
+				pn.translate(dx, dy);
+				pn.setText(valueTag.getTextContent(),null);
+				pnList.add(pn);
+			}
+			propertyNodesMap.put(type, pnList);
+			/* this will apply the modifier format to the properties */
+			reshapeOuterProperties(outsidePropertyTypes);
+		}
+	}
+	
+	@Override
+	protected void translateImplementation(Point2D p, double dx, double dy){
+		dataDisplayBounds.setFrame(dataDisplayBounds.getX() + dx,
+				dataDisplayBounds.getY() + dy, 
+				dataDisplayBounds.getWidth(), 
+				dataDisplayBounds.getHeight());
+		/* translate all the external property nodes  */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList)
+				pn.translate(dx, dy);
+	}
+	
+	@Override
+	public Object clone(){
+		SimpleShapeNode n = (SimpleShapeNode)super.clone();
+		n.propertyLabels = new MultiLineString[propertyLabels.length];
+		n.nameLabel = new MultiLineString();
+		n.propertyNodesMap = new LinkedHashMap<String, List<PropertyNode>>();
+		for(String s : propertyNodesMap.keySet())
+			n.propertyNodesMap.put(s, new LinkedList<PropertyNode>());
+		n.dataDisplayBounds = (Rectangle2D.Double)dataDisplayBounds.clone();
+		return n;
+	}
+	
+	protected boolean anyInsideProperties(){
+		boolean propInside = false;
+		for(String type : getProperties().getTypes()){
+	    	if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside){
+	    		if(!getProperties().getValues(type).isEmpty()){
+	    			propInside = true;
+	    			break;
+	    		}
+	    	}
+	    }
+		return propInside;
+	}
+	
+	protected Rectangle2D.Double dataDisplayBounds;
+	protected double boundsGap; 
+	protected boolean drawPropertySeparators = true;
+	protected MultiLineString[] propertyLabels;
+	protected MultiLineString nameLabel;
+	protected Map<String,List<PropertyNode>> propertyNodesMap;
+	
+	public static enum ShapeType  {Circle, Ellipse, Rectangle, Square, Triangle, Transparent};
+	public static enum Position  {Inside, Outside};
+	
+	private static final int DEFAULT_WIDTH = 100;
+    private static final int DEFAULT_HEIGHT = 60;
+	private static final Rectangle2D.Double minBounds = new Rectangle2D.Double(0,0,DEFAULT_WIDTH,DEFAULT_HEIGHT);
+	private static final int PROP_NODE_DIST = 50;
+	
+	protected static class PropertyNode{
+		public PropertyNode(ShapeType aShape){
+			/* add a little padding in the shape holding the label */
+			label = new MultiLineString(){
+				 public Rectangle2D getBounds(){
+					 Rectangle2D bounds = super.getBounds();
+					 if(bounds.getWidth() != 0 || bounds.getHeight() != 0){
+						 bounds.setFrame(
+								 bounds.getX(),
+								 bounds.getY(), 
+								 bounds.getWidth() + PADDING, 
+								 bounds.getHeight() + PADDING);
+					 }
+					 return bounds;
+				 }
+			};
+			label.setJustification(MultiLineString.CENTER);
+			shapeType = aShape;
+			shape  = label.getBounds();
+		}
+		
+		public void setText(String text, ModifierView[] views){
+			label.setText(text,views);
+			
+			switch(shapeType){
+			case Circle :
+				Rectangle2D circleBounds = EllipticalNode.getOutBounds(label.getBounds());	
+				shape = new Ellipse2D.Double(
+						circleBounds.getX(),
+						circleBounds.getY(),
+						Math.max(circleBounds.getWidth(),circleBounds.getHeight()),
+						Math.max(circleBounds.getWidth(),circleBounds.getHeight())
+					);
+				break;
+			case Ellipse :
+				Rectangle2D ellipseBounds = EllipticalNode.getOutBounds(label.getBounds());
+				shape = new Ellipse2D.Double(
+							ellipseBounds.getX(),
+							ellipseBounds.getY(),
+							ellipseBounds.getWidth(),
+							ellipseBounds.getHeight()
+						);
+				break;
+			case Triangle : 
+				shape = TriangularNode.getOutShape(label.getBounds());
+				break;
+			default : // Rectangle, Square and Transparent 
+				shape = label.getBounds();;
+				break;
+			}
+			
+			/* a new shape, placed at (0,0) has been created as a result of set text, therefore  *
+			 * we must put it back where the old shape was, since the translation is performed   * 
+			 * by adding the translate args to x and y, x and y must first be set to 0 			 */
+			double currentX = x;
+			double currentY = y;
+			x = 0;
+			y = 0;
+			translate(currentX,currentY);
+		}
+		
+		public void draw(Graphics2D g){
+			Color oldColor = g.getColor();
+			if(shapeType != ShapeType.Transparent){
+				g.translate(SHADOW_GAP, SHADOW_GAP);      
+				g.setColor(SHADOW_COLOR);
+				g.fill(shape);
+				g.translate(-SHADOW_GAP, -SHADOW_GAP);
+				
+				g.setColor(g.getBackground());
+				g.fill(shape);
+				g.setColor(Color.BLACK);
+				g.draw(shape);
+			}
+			
+			label.draw(g, shape.getBounds2D());		
+			g.setColor(oldColor);
+		}
+		
+		public void translate(double dx, double dy){
+			x += dx;
+			y += dy;
+			
+			if(shape instanceof Path2D){ //it's a triangle
+				Rectangle2D labelBounds = label.getBounds();
+				labelBounds.setFrame(
+						x,
+						y,
+						labelBounds.getWidth(),
+						labelBounds.getHeight()
+						);
+				shape = TriangularNode.getOutShape(labelBounds);
+			}else{
+				Rectangle2D bounds = shape.getBounds2D();
+				((RectangularShape)shape).setFrame(
+						x,
+						y,
+						bounds.getWidth(),
+						bounds.getHeight()
+				);
+			}
+		}
+		
+		public Point2D getConnectionPoint(Direction d) {
+			switch(shapeType){
+			case Circle :
+			case Ellipse :
+				return EllipticalNode.calculateConnectionPoint(d, shape.getBounds2D());
+			case Triangle :
+				return TriangularNode.calculateConnectionPoint(d, shape.getBounds2D());
+			default :
+				return RectangularNode.calculateConnectionPoint(d, shape.getBounds2D());
+			}
+		}
+		
+		public boolean contains(Point2D p){
+			return shape.contains(p);
+		}
+		
+		public Point2D getCenter(){
+			Rectangle2D bounds =  shape.getBounds2D() ;
+			return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
+		}
+		
+		double getX(){
+			return x;
+		}
+		
+		double getY(){
+			return y;
+		}
+		
+		private static final int PADDING = 5;
+		private MultiLineString label;
+		private ShapeType shapeType;
+		private Shape shape;
+		private double x;
+		private double y;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapePrototypePersistenceDelegate.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,258 @@
+/*  
+ 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.simpletemplate;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.LineStyle;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PrototypePersistenceDelegate;
+
+/**
+ *
+ * A PrototypePersistenceDelegate class which provides informations to be saved and restored by a PersistenceManager  
+ * in order to rebuild simple shaped nodes and edges out of an XML file.  
+ *
+ */
+public class SimpleShapePrototypePersistenceDelegate implements PrototypePersistenceDelegate{
+	
+	public void encodeNodePrototype(Document doc, Element parent, Node node){
+		SimpleShapeNode n = (SimpleShapeNode)node; 
+		Element typeTag = doc.createElement(PersistenceManager.TYPE);
+		typeTag.appendChild(doc.createTextNode(n.getType()));
+		parent.appendChild(typeTag);
+		
+		Element shapeType = doc.createElement(SHAPE_TYPE);
+		shapeType.appendChild(doc.createTextNode(n.getShapeType().toString()));
+		parent.appendChild(shapeType);
+		
+		Element propertiesTag = doc.createElement(PersistenceManager.PROPERTIES);
+		parent.appendChild(propertiesTag);
+		for(String type : n.getProperties().getTypes()){
+			Element propertyTag = doc.createElement(PersistenceManager.PROPERTY);
+			propertiesTag.appendChild(propertyTag);
+			
+			Element propertyTypeTag = doc.createElement(PersistenceManager.TYPE); 
+			propertyTypeTag.appendChild(doc.createTextNode(type));
+			propertyTag.appendChild(propertyTypeTag);
+			
+			Element viewTag = doc.createElement(VIEW);
+			propertyTag.appendChild(viewTag);
+			PropertyView view = (PropertyView)n.getProperties().getView(type);
+			viewTag.setAttribute(POSITION, view.getPosition().toString());
+			viewTag.setAttribute(SHAPE_TYPE, view.getShapeType().toString());
+			Modifiers modifiers = n.getProperties().getModifiers(type);
+			if(!modifiers.isNull()){
+				Element modifiersTag = doc.createElement(PersistenceManager.MODIFIERS);
+				propertyTag.appendChild(modifiersTag);
+				for(int i=0; i<modifiers.getTypes().size();i++){
+					String modifierType = modifiers.getTypes().get(i);
+					Element modifierTag = doc.createElement(PersistenceManager.MODIFIER);
+					modifiersTag.appendChild(modifierTag);
+					modifierTag.setAttribute(PersistenceManager.ID, String.valueOf(i));
+					
+					Element modifierTypeTag = doc.createElement(PersistenceManager.TYPE);
+					modifierTag.appendChild(modifierTypeTag);
+					modifierTypeTag.appendChild(doc.createTextNode(modifierType));
+					
+					ModifierView modifierView = (ModifierView)modifiers.getView(modifierType);
+					viewTag = doc.createElement(VIEW);
+					modifierTag.appendChild(viewTag);
+					viewTag.setAttribute(BOLD, modifierView.isBold()+"");
+					viewTag.setAttribute(UNDERLINE, modifierView.isUnderline()+"");
+					viewTag.setAttribute(ITALIC, modifierView.isItalic()+"");
+					viewTag.setAttribute(PREFIX, modifierView.getPrefix());
+					viewTag.setAttribute(SUFFIX, modifierView.getSuffix());
+				}
+			}
+		}
+	}
+	
+	public void encodeEdgePrototype(Document doc, Element parent, Edge edge){
+		SimpleShapeEdge e = (SimpleShapeEdge)edge;
+		Element typeTag = doc.createElement(PersistenceManager.TYPE);
+		typeTag.appendChild(doc.createTextNode(e.getType()));
+		parent.appendChild(typeTag);
+		
+		Element lineStyleTag = doc.createElement(LINE_STYLE);
+		lineStyleTag.appendChild(doc.createTextNode(e.getStyle().toString()));
+		parent.appendChild(lineStyleTag);
+		
+		Element minNodesTag = doc.createElement(MIN_ATTACHED_NODES);
+		parent.appendChild(minNodesTag);
+		minNodesTag.appendChild(doc.createTextNode(e.getMinAttachedNodes()+""));
+		
+		Element maxNodesTag = doc.createElement(MAX_ATTACHED_NODES);
+		parent.appendChild(maxNodesTag);
+		maxNodesTag.appendChild(doc.createTextNode(e.getMaxAttachedNodes()+""));
+		
+		if(e.getHeads() != null) 
+			if(e.getHeads().length != 0){
+				Element headsTag = doc.createElement(HEADS);
+				parent.appendChild(headsTag);
+				for(int i=0; i<e.getHeads().length; i++){
+				    ArrowHead head = e.getHeads()[i];
+					Element headTag = doc.createElement(HEAD);
+					headsTag.appendChild(headTag);
+					headTag.setAttribute(HEAD, head.toString());
+					headTag.setAttribute(HEAD_DESCRIPTION, e.getAvailableEndDescriptions()[i]);
+				}
+			}
+	}
+	
+	public Node decodeNodePrototype(Element root) throws IOException{
+		if(root.getElementsByTagName(PersistenceManager.TYPE).item(0) == null || 
+				root.getElementsByTagName(SHAPE_TYPE).item(0) == null)
+			throw new IOException();
+		
+		String typeName = root.getElementsByTagName(PersistenceManager.TYPE).item(0).getTextContent();
+		String shapeTypeName = root.getElementsByTagName(SHAPE_TYPE).item(0).getTextContent();
+		SimpleShapeNode.ShapeType shapeType;
+		try {
+			shapeType = SimpleShapeNode.ShapeType.valueOf(shapeTypeName);
+		}catch(IllegalArgumentException e){
+			throw new IOException();
+		}
+			
+		NodeList propertyTagList = root.getElementsByTagName(PersistenceManager.PROPERTY);
+		LinkedHashMap<String,Set<String>> propertyTypeDefinition = new LinkedHashMap<String,Set<String>>();
+		Map<String, PropertyView> propertyViews = new LinkedHashMap<String, PropertyView>();
+		Map<String, Map<String,ModifierView>> modifiersView = new LinkedHashMap<String, Map<String,ModifierView>>();
+		
+		for(int i = 0 ; i< propertyTagList.getLength(); i++){
+			Element property = (Element)propertyTagList.item(i);
+			if(property.getElementsByTagName(PersistenceManager.TYPE).item(0) == null || 
+					property.getElementsByTagName(VIEW).item(0) == null)
+				throw new IOException();
+			
+			String propertyType = property.getElementsByTagName(PersistenceManager.TYPE).item(0).getTextContent();
+			Element viewTag = (Element) property.getElementsByTagName(VIEW).item(0);
+			viewTag.getAttributes();
+			try{
+				PropertyView propertyView = new PropertyView(
+						Enum.valueOf(SimpleShapeNode.Position.class,viewTag.getAttribute(POSITION)),
+						Enum.valueOf(SimpleShapeNode.ShapeType.class,viewTag.getAttribute(SHAPE_TYPE))
+						);
+				propertyViews.put(propertyType, propertyView);
+			}catch(IllegalArgumentException e){
+				throw new IOException(e);
+			}
+			
+			NodeList modifierTagList = property.getElementsByTagName(PersistenceManager.MODIFIER);
+			Set<String> modifierTypeDefinition = null;
+			/* modifierViewsValue is the Map to be eventually put into the modifierViews as a value */
+			/* (can be null), the key being the current property (the for cycle current index one)  */
+			Map<String,ModifierView> modifierViewsValue = null;
+			if(modifierTagList.getLength() > 0){
+			    modifierTypeDefinition = new LinkedHashSet<String>();
+			    modifierViewsValue = new LinkedHashMap<String,ModifierView>();
+			}
+			for(int j=0; j<modifierTagList.getLength();j++ ){
+				Element modifierTag = (Element)modifierTagList.item(j);
+				if(modifierTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null || 
+						modifierTag.getElementsByTagName(VIEW).item(0) == null)
+					throw new IOException();
+				String modifierType = modifierTag.getElementsByTagName(PersistenceManager.TYPE).item(0).getTextContent();
+				modifierTypeDefinition.add(modifierType);
+				
+				Element modifierViewTag = (Element) modifierTag.getElementsByTagName(VIEW).item(0);
+				ModifierView modifierView = new ModifierView(
+						Boolean.parseBoolean(modifierViewTag.getAttribute(UNDERLINE)),
+						Boolean.parseBoolean(modifierViewTag.getAttribute(BOLD)),
+						Boolean.parseBoolean(modifierViewTag.getAttribute(ITALIC)),
+						modifierViewTag.getAttribute(PREFIX),
+						modifierViewTag.getAttribute(SUFFIX)
+				);
+				modifierViewsValue.put(modifierType, modifierView);
+			}
+			if(modifierTagList.getLength() > 0){
+				modifiersView.put(propertyType, modifierViewsValue);
+			}
+			propertyTypeDefinition.put(propertyType, modifierTypeDefinition);
+		}
+		
+		/* create the properties and set the views */
+		NodeProperties prps = new NodeProperties(propertyTypeDefinition);
+		for(String propertyType : propertyViews.keySet()){
+			prps.setView(propertyType, propertyViews.get(propertyType));
+			if(modifiersView.get(propertyType) != null)
+				for(String modifierType : modifiersView.get(propertyType).keySet()){
+					prps.getModifiers(propertyType).setView(modifierType, modifiersView.get(propertyType).get(modifierType));
+				}
+		}
+		
+		return SimpleShapeNode.getInstance(shapeType, typeName, prps);
+	}
+	
+	public Edge decodeEdgePrototype(Element root) throws IOException{
+		if(root.getElementsByTagName(PersistenceManager.TYPE).item(0) == null || 
+				root.getElementsByTagName(LINE_STYLE).item(0) == null || 
+				root.getElementsByTagName(MIN_ATTACHED_NODES).item(0) == null ||
+				root.getElementsByTagName(MAX_ATTACHED_NODES).item(0) == null)
+			throw new IOException();
+		String typeName = root.getElementsByTagName(PersistenceManager.TYPE).item(0).getTextContent();
+		LineStyle lineStyle = null;
+		try{
+			lineStyle = LineStyle.valueOf(root.getElementsByTagName(LINE_STYLE).item(0).getTextContent());
+		}catch(IllegalArgumentException e){
+			throw new IOException(e);
+		}
+		int minAttachedNodes = Integer.parseInt(root.getElementsByTagName(MIN_ATTACHED_NODES).item(0).getTextContent());
+		int maxAttachedNodes = Integer.parseInt(root.getElementsByTagName(MAX_ATTACHED_NODES).item(0).getTextContent());
+	
+		NodeList headTagList = root.getElementsByTagName(HEAD);
+		ArrowHead[] heads = new ArrowHead[headTagList.getLength()];
+		String[] headDescriptions = new String[headTagList.getLength()];
+		
+		for(int i=0;i<headTagList.getLength();i++){
+			Element headTag = (Element)headTagList.item(i);
+			heads[i] = ArrowHead.getArrowHeadFromString(headTag.getAttribute(HEAD));
+			headDescriptions[i] = headTag.getAttribute(HEAD_DESCRIPTION);
+		}
+		return new SimpleShapeEdge(typeName, lineStyle, heads, headDescriptions, minAttachedNodes, maxAttachedNodes);
+	}
+	
+	public static final String SHAPE_TYPE = "ShapeType";
+	public static final String VIEW = "View";
+	public static final String BOLD = "Bold";
+	public static final String ITALIC = "Italic";
+	public static final String UNDERLINE = "Underline";
+	public static final String PREFIX = "Prefix";
+	public static final String SUFFIX = "Suffix";
+	public static final String POSITION = "Position";
+	public static final String LINE_STYLE = "LineStyle";
+	public static final String MIN_ATTACHED_NODES = "MinAttachedNodes";
+	public static final String MAX_ATTACHED_NODES = "MaxAttachedNodes";
+	public static final String HEADS = "Heads";
+	public static final String HEAD = "Head";
+	public static final String HEAD_DESCRIPTION = "headLabel";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleTemplateEditor.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,59 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Frame;
+import java.util.Collection;
+import java.util.ResourceBundle;
+
+import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+import uk.ac.qmul.eecs.ccmi.gui.TemplateEditor;
+
+/**
+ *
+ * The implementation of the TemplateEditor interface which uses a Wizard 
+ * to allow the user to define the templates to be created.
+ *
+ */
+public class SimpleTemplateEditor implements TemplateEditor {
+
+	@Override
+	public Diagram createNew(Frame frame, Collection<String> existingTemplates) {
+		Wizard wizard = new Wizard(frame,existingTemplates);
+		return wizard.execute();
+	}
+
+	@Override
+	public Diagram edit(Frame frame, Collection<String> existingTemplates,
+			Diagram diagram) {
+		Wizard wizard = new Wizard(frame,existingTemplates,diagram);
+		return wizard.execute();
+	}
+
+	@Override
+	public String getLabelForNew(){
+		return ResourceBundle.getBundle(SpeechWizardDialog.class.getName()).getString("wizard_new_label");
+	}
+	
+	@Override
+	public String getLabelForEdit(){
+		return ResourceBundle.getBundle(SpeechWizardDialog.class.getName()).getString("wizard_edit_label");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,94 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JRootPane;
+import javax.swing.KeyStroke;
+import javax.swing.event.ChangeEvent;
+
+import jwizardcomponent.dialog.SimpleJWizardDialog;
+
+/*
+ * The dialog where the template wizard is displayed 
+ * 
+ * @see Wizard
+ *
+ */
+@SuppressWarnings("serial")
+public class SpeechWizardDialog extends SimpleJWizardDialog {
+	public SpeechWizardDialog(Frame owner){
+		super(owner,true);
+		finished = false;
+		
+		ResourceBundle resources = ResourceBundle.getBundle(getClass().getName());
+		setSize(350, 200);
+		setTitle(resources.getString("dialog.wizard.title"));
+		setLocationRelativeTo(owner);
+		
+		JButton button;
+		button = getWizardComponents().getNextButton();
+		button.setText(resources.getString("button.next.label"));
+		button.getAccessibleContext().setAccessibleName(resources.getString("button.next.speech"));
+		
+		button = getWizardComponents().getBackButton();
+		button.setText(resources.getString("button.previous.label"));
+		button.getAccessibleContext().setAccessibleName(resources.getString("button.previous.speech"));
+		
+		button = getWizardComponents().getCancelButton(); 	
+		button.setText(resources.getString("button.cancel.label"));
+		
+		button = getWizardComponents().getFinishButton();
+		button.setText(resources.getString("button.finish.label"));
+		button.addChangeListener(new javax.swing.event.ChangeListener(){
+			@Override
+			public void stateChanged(ChangeEvent e) {
+				((JButton)e.getSource()).setEnabled(finished);
+			}
+		});
+		JRootPane rootPane = getRootPane();
+		rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "close");
+		rootPane.getActionMap().put("close", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				dispose();
+			}
+		});
+	}
+	
+	/**
+	 * Enables or disables the finish button.   
+	 * @param enabled
+	 */
+	public void setFinishButtonEnabled(boolean enabled){
+		finished = enabled;
+		getWizardComponents().getFinishButton().setEnabled(true);
+	}
+	
+	private boolean finished;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,99 @@
+wizard_new_label=New Template
+wizard_edit_label=Edit Template
+
+dialog.wizard.title=Template Creation Dialog
+dialog.error.existing_value=Error: value {0} already exists
+dialog.error.empty_text=Error: text cannot be empty
+dialog.error.empty_desc=Error: {0} label cannot be empty
+dialog.error.min_max=Error: minimum value cannot be greater than maximum value
+dialog.summary.title=Template summary
+dialog.summary.ok_button_label=Create Template
+dialog.summary.cancel_button_label=Back to Editing
+dialog.error.node_type_not_present=Node type {0} not present in template definition
+dialog.error.edge_type_not_present=Edge type {0} not present in template definition
+
+
+panel.home.title.new=What would you like to do?
+panel.home.choice.diagram_name=Edit Diagram Name
+panel.home.choice.nodes=Edit Diagram Nodes
+panel.home.choice.edges=Edit Diagram Edges
+panel.home.choice.finish=Finish Editing Template
+
+panel.diagram_name.title=Enter Diagram Name
+panel.diagram_name.title.editing_existing_diagram=Enter Diagram Name (different from the starting diagram's)
+
+panel.nodes.title=Select Action to perform for Nodes
+panel.nodes.actions.add=Add New Node
+panel.nodes.actions.edit=Edit Existing Node
+panel.nodes.actions.del=Delete Existing Node
+panel.nodes.actions.finish=Finish Editing Nodes
+
+panel.node_edit.title=Select Node to edit
+panel.node_del.title=Select Node to delete
+panel.node_name.title=Enter Node name
+panel.node_shape.title=Select Node shape
+
+panel.yesno_properties.title=Would you like to add any Properties?
+panel.yesno_properties.add=Yes
+panel.yesno_properties.finish=No
+
+panel.properties.title=Select Action to perform for Properties
+panel.properties.actions.add=Add New Property
+panel.properties.actions.edit=Edit Existing Property
+panel.properties.actions.del=Delete Existing Property
+panel.properties.action.finish=Finish Editing Properties
+
+panel.property_del.title=Select Property to delete
+panel.property_edit.title=Select Property to edit
+panel.property_name.title=Enter Property Name
+panel.property_shape.title=Select property shape
+panel.property_position.title=Where would you like to place the Property ?
+
+panel.yesno_modifiers.title=Would you like to add any Modifiers?
+panel.yesno_modifiers.add=Yes
+panel.yesno_modifiers.finish=No
+
+panel.modifiers.title=Select Action to perform for Modifiers
+panel.modifiers.actions.add=Add New Modifier
+panel.modifiers.actions.edit=Edit Existing Modifier
+panel.modifiers.actions.del=Delete Existing Modifier
+panel.modifiers.actions.finish=Finish Editing Modifiers
+
+panel.modifier_type.title=Enter Modifier Name
+panel.modifier_del.title=Select Modifier to Delete
+panel.modifier_edit.title=Select Modifier to Edit
+panel.modifier_format.title=Select Modifier Format
+
+panel.edges.title=Select Action to perform for Edges
+panel.edges.actions.add=Add New Edge
+panel.edges.actions.edit=Edit Existing Edge
+panel.edges.actions.del=Delete Existing Edge
+panel.edges.actions.finish=Finish Editing Edges
+
+panel.edge_name.title=Enter Edge Name
+panel.edge_del.title=Select Edge to Delete
+panel.edge_edit.title=Select Edge to Edit
+panel.edge_linestyle.title=Select Edge Line Style 
+panel.edge_min_nodes.title=Select Minimum Nodes To Connect
+panel.edge_max_nodes.title=Select Maximum Nodes To Connect
+panel.edge_yesno_arrow_head.title=Would you like to add arrow heads?
+panel.edge_yesno_arrow_head.actions.add=Yes
+panel.edge_yesno_arrow_head.actions.finish=No
+panel.edge_arrow_head.title=Select arrow heads and set their labels
+
+modifier.format.bold=Bold
+modifier.format.underline=Underline
+modifier.format.italic=Italic
+modifier.format.prefix=Prefix
+modifier.format.suffix=Suffix
+
+button.next.label=Next >
+button.next.speech=Next
+button.previous.label=< Back
+button.previous.speech=Back
+button.finish.label=Done
+button.cancel.label=Cancel
+
+
+file={0}, File
+dir={0}, Directory
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardPanel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,319 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.ResourceBundle;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFormattedTextField;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+import javax.swing.SpinnerModel;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import jwizardcomponent.JWizardComponents;
+import jwizardcomponent.JWizardPanel;
+import uk.ac.qmul.eecs.ccmi.gui.LoopComboBox;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
+
+/*
+ * The abstract class providing basic implementation for the panels displayed when the template
+ * wizard is run in order to build a diagram template. Subclasses will define the central component 
+ * displayed in the panel. The central component is an input component (e.g. a JTextField),
+ * through which the user enters the input required for at that particular step of the wizard.
+ *  
+ *  
+ * @see Wizard
+ */
+@SuppressWarnings("serial")
+abstract class SpeechWizardPanel extends JWizardPanel {
+	public SpeechWizardPanel(JWizardComponents wizardComponents, String title, int next, int previous){
+		super(wizardComponents,title);
+		label = new JLabel(title);
+		this.next = next;
+		this.previous = previous;
+	}
+	
+	@Override
+	public void update(){
+		Component focusOwner = assignFocus();
+		NarratorFactory.getInstance().speak(
+				new StringBuilder(getPanelTitle())
+					.append(' ')
+					.append(SpeechUtilities.getComponentSpeech(focusOwner)).toString());
+		super.update();
+	}
+	
+	@Override
+	public void setPanelTitle(String title){
+		label.setText(title);
+		super.setPanelTitle(title);
+	}
+	
+	protected Component assignFocus(){
+		if(component != null)
+			component.requestFocus();
+		return component;
+	}
+	
+	/**
+	 * Lays out the components according to the layout manager. This method is used by subclasses 
+	 * by passing the component the user use for input (e.g. a text field or a combo-box) as argument.
+	 * such component is placed at the centre of the panel above the buttons.    
+	 * @param centralComponent the component to be laid out at the centre dialog  
+	 */
+	protected void layoutComponents(JComponent centralComponent){
+		component = centralComponent;
+		/* pressing enter on the central component results in a switch to the next panel */
+		component.addKeyListener(new KeyAdapter(){
+			@Override
+			public void keyPressed(KeyEvent evt){
+				pressed = true;
+			}
+			
+			@Override
+			public void keyTyped(KeyEvent evt){
+				/* switch on the next panel only if the press button started on the same window      * 
+				 * this is to avoid keyTyped to be called after the panel switch and therefore refer *
+				 * to a component different that the one the user pressed OK on                      */
+				if(evt.getKeyChar() == '\n' && pressed)
+					getWizardComponents().getNextButton().doClick();
+				pressed = false;
+			}
+			boolean pressed = false;
+		});
+		
+		GridBagConstraints constr = new GridBagConstraints();
+		constr.gridx = 0;
+		constr.gridy = 0;
+		constr.gridwidth = 1;
+		constr.gridheight = 1;
+		constr.weightx = 1.0;
+		constr.weighty = 0.0;
+		constr.anchor = GridBagConstraints.PAGE_START;
+		constr.fill = GridBagConstraints.BOTH;
+		constr.insets = new Insets(5, 5, 5, 5);
+		constr.ipadx = 0;
+		constr.ipady = 0;
+		
+		/* Label */
+		setLayout(new GridBagLayout());
+		JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+		label.setHorizontalAlignment(SwingConstants.LEADING);
+		labelPanel.add(label);
+		add(labelPanel,constr);
+		
+		/* JSeparator */
+		constr.gridy = 1;
+		constr.anchor = GridBagConstraints.WEST;
+		constr.fill = GridBagConstraints.BOTH;
+		constr.insets = new Insets(1, 1, 1, 1);
+		add(new JSeparator(), constr);
+		
+		/* central component */
+		Container centralComponentContainer;
+		if(centralComponent instanceof JScrollPane ){
+			centralComponentContainer = centralComponent;
+		}else{
+			centralComponentContainer = new JPanel(new GridBagLayout());
+			centralComponentContainer.add(centralComponent
+	        , new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0
+	        , GridBagConstraints.CENTER, GridBagConstraints.BOTH
+	        , new Insets(0, 0, 0, 0), 0, 0));
+		}
+		constr.gridy = 2;
+		constr.weighty = 1.0;
+		constr.anchor = GridBagConstraints.CENTER;
+		constr.insets = new Insets(0, 0, 0, 0); 
+		add(centralComponentContainer,constr);
+	}
+	
+	@Override
+	public void next(){
+		switchPanel(next);
+	}
+
+	@Override
+	public void back(){
+		switchPanel(previous);
+	}
+	
+	private JLabel label;
+	private int next;
+	private int previous;
+	private JComponent component;
+	public static int OWN_SWITCH = -1;
+	public static int DISABLE_SWITCH = -2;
+}
+
+@SuppressWarnings("serial")
+class SelectWizardPanel extends SpeechWizardPanel {
+	SelectWizardPanel(JWizardComponents wizardComponents, String title, Collection<String> options, int next, int previous, Model.Record record){
+		super(wizardComponents,title,next,previous);
+		String[] optionsArray = new String[options.size()];
+		comboBox = new LoopComboBox(new DefaultComboBoxModel(options.toArray(optionsArray)));
+		comboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
+		layoutComponents(comboBox);
+		this.record = record;
+	}
+	
+	SelectWizardPanel(JWizardComponents wizardComponents, String title, Collection<String> options, int[] nexts, int previous, Model.Record record){
+		this(wizardComponents, title, options, OWN_SWITCH, previous, record);
+		this.nexts = nexts;
+	}
+	
+	SelectWizardPanel(JWizardComponents wizardComponents, String title, Collection<String> options, int[] nexts, int previous){
+		this(wizardComponents, title, options, nexts, previous,null);
+	}
+
+	@Override
+	public void next(){
+		if(record != null)
+			record.value = (String)comboBox.getSelectedItem();
+		if(nexts != null)
+			switchPanel(nexts[comboBox.getSelectedIndex()]);
+		else
+			super.next();
+	}
+	
+	@Override
+	public void update(){
+		if(record != null)
+			comboBox.setSelectedItem(record.value);
+		super.update();
+	}
+	
+	JComboBox comboBox;
+	int[] nexts;
+	Model.Record record;
+}
+
+@SuppressWarnings("serial")
+class TextWizardPanel extends SpeechWizardPanel {
+	TextWizardPanel(JWizardComponents wizardComponents, String title, Collection<String> existingValues, int next, int previous, Model.Record record){
+		super(wizardComponents,title,next,previous);
+		textField = new JTextField();
+		textField.setColumns(10);
+		textField.addKeyListener(SpeechUtilities.getSpeechKeyListener(true));
+		layoutComponents(textField);
+		this.record = record;
+		this.existingValues = existingValues;
+	}
+	
+	public void next(){
+		String text = textField.getText().trim();
+		/* if the user enters a text he has already entered (that is, it's in the existingValues the don't go on */
+		/* and notify the user they have to chose another text. The only exception is when the record contains   */ 
+		/* the same text the user entered as that means they are going through the editing of an existing element*/
+		if(text.isEmpty()||"\n".equals(text)){
+				NarratorFactory.getInstance().speak(ResourceBundle.getBundle(SpeechWizardDialog.class.getName()).getString("dialog.error.empty_text"));
+				return;
+		}
+		for(String value : existingValues){
+			if(value.equals(text) && !text.equals(record.value)){
+				NarratorFactory.getInstance().speak(MessageFormat.format( 
+								ResourceBundle.getBundle(SpeechWizardDialog.class.getName()).getString("dialog.error.existing_value"),
+								text));
+				return;
+			}
+		}
+		if(record != null)
+			record.value = text;
+		super.next();
+	}
+	
+	@Override
+	public void update(){
+		if(record != null)
+			textField.setText(record.value);
+		super.update();
+	}
+	
+	JTextField textField;
+	Collection<String> existingValues;
+	Model.Record record;
+}
+
+@SuppressWarnings("serial")
+class SpinnerWizardPanel extends SpeechWizardPanel{
+	public SpinnerWizardPanel(JWizardComponents wizardComponents, String title, SpinnerModel spinnerModel, int next, int previous, Model.Record record){
+		super(wizardComponents,title,next,previous);
+		this.record = record;
+		spinner = new JSpinner(spinnerModel);
+		spinner.addChangeListener(new ChangeListener(){
+			@Override
+			public void stateChanged(ChangeEvent evt) {
+				 JSpinner s = (JSpinner)(evt.getSource());
+				 NarratorFactory.getInstance().speak(s.getValue().toString());
+			}
+		});
+		JFormattedTextField tf = ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField();
+		tf.setEditable(false);
+		tf.setFocusable(false);
+		tf.setBackground(Color.white);
+		layoutComponents(spinner);
+	}
+	
+	@Override
+	public void next(){
+		if(record != null)
+			record.value = spinner.getValue().toString();
+		super.next();
+	}
+	
+	@Override
+	public void update(){
+		if(record != null){
+			if(!record.value.isEmpty())
+				spinner.setValue(Integer.parseInt(record.value));
+		}
+		super.update();
+	}
+	
+	Model.Record record;
+	JSpinner spinner;
+}
+
+@SuppressWarnings("serial")
+class DummyWizardPanel extends JWizardPanel{
+	DummyWizardPanel(JWizardComponents wizardComponents){
+		super(wizardComponents);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SquareNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -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.simpletemplate;
+
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.InputStream;
+import java.util.List;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+
+/**
+ * 
+ * A squared shaped diagram node.
+ *
+ */
+@SuppressWarnings("serial")
+public class SquareNode extends RectangularNode {
+	
+	public SquareNode(String nodeType, NodeProperties properties){
+		super(nodeType, properties);
+		dataDisplayBounds = getMinBounds();
+		sqShape = getMinBounds();
+	}
+	
+	@Override
+	protected void reshapeInnerProperties(List<String> insidePropertyTypes){
+		super.reshapeInnerProperties(insidePropertyTypes);
+		
+		double diffwh = dataDisplayBounds.getWidth() - dataDisplayBounds.getHeight();
+		if(diffwh > 0){
+			sqShape.setFrame(dataDisplayBounds.getX(),dataDisplayBounds.getY()-diffwh/2,dataDisplayBounds.getWidth(),dataDisplayBounds.getWidth());
+		} else if(diffwh < 0){
+			sqShape.setFrame(dataDisplayBounds.getX()+diffwh/2,dataDisplayBounds.getY(),dataDisplayBounds.getHeight(),dataDisplayBounds.getHeight());
+		}else{
+			sqShape.setFrame(dataDisplayBounds);
+		}
+	}
+	
+	public Rectangle2D.Double getMinBounds(){
+		Rectangle2D r = super.getMinBounds();
+		r.setFrame(r.getX(), r.getY(), r.getHeight(), r.getHeight());
+		return (Rectangle2D.Double)r;
+	}
+	
+	@Override
+	public ShapeType getShapeType(){
+		return ShapeType.Square;
+	}
+	
+	@Override
+	public InputStream getSound(){
+		return sound;
+	}
+	
+	@Override
+	protected void translateImplementation(Point2D p, double dx, double dy){
+		/* if we clicked on a property node, just move that one */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList)
+				if(pn.contains(p)){
+					pn.translate(dx, dy);
+					return;
+				}
+		
+		sqShape.setFrame(sqShape.getX() + dx,
+				sqShape.getY() + dy, 
+				sqShape.getWidth(), 
+				sqShape.getHeight());
+		super.translateImplementation(p,dx, dy);
+	}
+	
+	@Override
+	public Rectangle2D getBounds(){
+		return (Rectangle2D)sqShape.clone();
+	}
+	
+	@Override
+	public Shape getShape(){
+		return sqShape;
+	}
+	
+	@Override
+	public Object clone(){
+		SquareNode n = (SquareNode)super.clone();
+		n.sqShape = (Rectangle2D.Double)sqShape.clone();
+		return n;
+	}
+	private Rectangle2D.Double sqShape;
+	private static InputStream sound;
+	
+	static{
+		sound = SquareNode.class.getResourceAsStream("audio/Square.mp3");
+		SoundFactory.getInstance().loadSound(sound);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/TriangularNode.java	Fri Dec 16 17:35:51 2011 +0000
@@ -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.simpletemplate;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Rectangle2D.Double;
+import java.io.InputStream;
+import java.util.List;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.gui.Direction;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+
+/**
+ * 
+ * A triangular shaped diagram node.
+ *
+ */
+@SuppressWarnings("serial")
+public class TriangularNode extends SimpleShapeNode {
+
+
+	public TriangularNode(String typeName, NodeProperties properties) {
+		super(typeName, properties);
+		Rectangle2D dataBounds = getMinBounds();  
+		dataDisplayBounds.setFrame(dataBounds);
+		tShape = getOutShape(dataBounds);
+		/* by building the shape around dataBounds which was at (0,0) the new bounds */
+		/* are now negative, so we need to bring the new bounds back at (0,0)        */
+		Rectangle2D bounds = getBounds();
+		translateImplementation(new Point2D.Double(),0-bounds.getX(),0-bounds.getY());
+	}
+	
+	@Override
+	protected Rectangle2D getMinBounds(){
+		Rectangle2D minBounds = super.getMinBounds();
+		return  new Rectangle2D.Double(minBounds.getX(),minBounds.getY(),minBounds.getWidth()/2,minBounds.getHeight()/2); 
+	}
+
+	@Override
+	public ShapeType getShapeType() {
+		return ShapeType.Triangle;
+	}
+	
+	@Override
+	protected void translateImplementation(Point2D p, double dx, double dy){
+		/* if we clicked on a property node, just move that one */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList)
+				if(pn.contains(p)){
+					pn.translate(dx, dy);
+					return;
+				}
+		super.translateImplementation(p,dx, dy);
+		tShape.transform(AffineTransform.getTranslateInstance(dx, dy));
+	}
+
+	public static Path2D.Double getOutShape(Rectangle2D r){
+		Path2D.Double triangle = new Path2D.Double(GeneralPath.WIND_EVEN_ODD,3);
+		double minEdge = Math.min(r.getWidth(), r.getHeight());
+		triangle.moveTo(r.getCenterX(), r.getY()-minEdge);
+		
+		double angle = Math.atan(minEdge/(r.getWidth()/2));
+		double w = r.getHeight()/ Math.tan(angle);
+		triangle.lineTo(r.getX()-w, r.getMaxY());
+		triangle.lineTo(r.getMaxX()+w, r.getMaxY());
+		triangle.closePath();
+		return triangle;
+	}
+	
+	@Override
+	protected void reshapeInnerProperties(List<String> insidePropertyTypes){
+		nameLabel = new MultiLineString();
+		nameLabel.setText(getName().isEmpty() ? " " : getName());
+		nameLabel.setBold(true);
+		
+		if(!super.anyInsideProperties()){
+			dataDisplayBounds.setFrame(dataDisplayBounds.getX(),
+					dataDisplayBounds.getY(),
+					nameLabel.getBounds().getWidth(),
+					nameLabel.getBounds().getHeight());
+			Rectangle2D minBounds = getMinBounds();
+			dataDisplayBounds.add(new Rectangle2D.Double(dataDisplayBounds.getX(), dataDisplayBounds.getY(), minBounds.getWidth(),minBounds.getHeight()));
+			tShape = getOutShape(dataDisplayBounds);
+		}else {
+			Rectangle2D r = nameLabel.getBounds();
+			
+			for(int i=0; i<insidePropertyTypes.size();i++){
+				propertyLabels[i] = new MultiLineString();
+		    	if(getProperties().getValues(insidePropertyTypes.get(i)).size() == 0){
+		    		propertyLabels[i].setText(" ");
+		    	}else{
+		    		propertyLabels[i].setJustification(MultiLineString.LEFT);
+		    		String[] a = new String[getProperties().getValues(insidePropertyTypes.get(i)).size()];
+		    		propertyLabels[i].setText(getProperties().getValues(insidePropertyTypes.get(i)).toArray(a), getProperties().getModifiers(insidePropertyTypes.get(i)));
+		    	}
+		    	r.add(new Rectangle2D.Double(r.getX(),r.getMaxY(),propertyLabels[i].getBounds().getWidth(),propertyLabels[i].getBounds().getHeight()));
+	    	}
+			/* set a gap to uniformly distribute the extra space among property rectangles to reach the minimum bound's height */
+			boundsGap = 0;
+			Rectangle2D.Double minBounds = (Rectangle2D.Double)getMinBounds();
+			if(r.getHeight() < minBounds.height){
+				boundsGap = minBounds.height - r.getHeight();
+				boundsGap /= insidePropertyTypes.size();
+			}
+			r.add(minBounds);
+			dataDisplayBounds.setFrame(new Rectangle2D.Double(dataDisplayBounds.x,dataDisplayBounds.y,r.getWidth(),r.getHeight()));
+			
+			tShape = getOutShape(dataDisplayBounds);
+		}
+
+	}
+	
+	@Override
+	public Double getBounds() {
+		return (Double)tShape.getBounds2D();
+	}
+	
+	@Override
+	public InputStream getSound(){
+		return sound;
+	}
+
+	@Override
+	public Point2D getConnectionPoint(Direction d) {
+		return calculateConnectionPoint(d,getBounds());
+	}
+	
+	public static Point2D calculateConnectionPoint(Direction d, Rectangle2D bounds) {
+		if(d.getX() == 0){
+			return new Point2D.Double(bounds.getCenterX(), 
+					d.getY() > 0 ? bounds.getY() : bounds.getMaxY());
+		}
+		
+		boolean left = false;
+		boolean right = false;
+		double dirTan = d.getY()/d.getX();
+		double boundsTan = bounds.getHeight()/bounds.getWidth();
+		double alfa = Math.atan(dirTan);		
+		double alfaDegrees = Math.toDegrees(alfa);
+		
+		if(d.getY() < 0){ //from the top
+			if(alfaDegrees < 0) 
+				right = true;
+			else 
+				left = true;
+		}else{ //from the bottom
+			if(dirTan < boundsTan && d.getX() > 0)
+				right = true;
+			else if(dirTan > -boundsTan && d.getX() < 0)
+				left = true;
+		}
+		
+		if(right){
+			double beta = Math.atan(bounds.getHeight()/(bounds.getWidth()/2));
+			double py = bounds.getHeight()/2;
+			double x = py/ (Math.tan(alfa)-Math.tan(beta));
+			double y = x * Math.tan(alfa);
+			return new Point2D.Double(bounds.getCenterX()-x, bounds.getCenterY()-y);
+		}
+		else if(left){
+			double beta = - Math.atan(bounds.getHeight()/(bounds.getWidth()/2));
+			double py = bounds.getHeight()/2;
+			double x = py/ (Math.tan(alfa)-Math.tan(beta));
+			double y = x * Math.tan(alfa);
+			return new Point2D.Double(bounds.getCenterX()-x, bounds.getCenterY()-y);
+		}
+		else{
+			return new Point2D.Double(
+					bounds.getCenterX() + ((bounds.getHeight()/2) * (d.getX()/d.getY()) ), 
+					bounds.getMaxY());
+		}
+	}
+
+	@Override
+	public Shape getShape() {
+		return tShape;
+	}
+	
+	public Object clone(){
+		return new TriangularNode(getType(),(NodeProperties)getProperties().clone());
+	}
+	
+	private Path2D.Double tShape;
+	private static InputStream sound;
+	
+	static {
+		sound = TriangularNode.class.getResourceAsStream("audio/Triangle.mp3");
+		SoundFactory.getInstance().loadSound(sound);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Wizard.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,1209 @@
+/*  
+ 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.simpletemplate;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SpinnerModel;
+
+import jwizardcomponent.FinishAction;
+import jwizardcomponent.JWizardComponents;
+import jwizardcomponent.JWizardPanel;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
+import uk.ac.qmul.eecs.ccmi.gui.Diagram;
+import uk.ac.qmul.eecs.ccmi.gui.Edge;
+import uk.ac.qmul.eecs.ccmi.gui.LineStyle;
+import uk.ac.qmul.eecs.ccmi.gui.LoopComboBox;
+import uk.ac.qmul.eecs.ccmi.gui.LoopSpinnerNumberModel;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.SpeechSummaryPane;
+import uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleShapeNode.ShapeType;
+import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
+import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
+import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
+import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
+import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities;
+
+/**
+ *
+ * A Wizard-like sequence of screens prompted to the user to let they input (e.g. which shape a node will have or how many nodes an edge can connect at most) 
+ * how to build a template diagram. A template diagram is a prototype diagram
+ * (containing prototype nodes and edges) which can later on be used for creating instances
+ * of that type of diagram through clonation. The wizard is completely accessible via audio 
+ * as all the content and all focused components names are spoken out by the {@link Narrator} through a text to speech synthesizer.   
+ *
+ */
+public class Wizard {
+	public Wizard(Frame frame, Collection<String> existingDiagrams, Diagram diagramToEdit){
+		dialog = new SpeechWizardDialog(frame);
+		resources = ResourceBundle.getBundle(SpeechWizardDialog.class.getName());
+		
+		model = createModel(diagramToEdit);
+		node = new Model.Node();
+		edge = new Model.Edge();
+		property = new Model.Property();
+		modifier = new Model.Modifier();
+		
+		initWizardComponents(existingDiagrams,diagramToEdit);
+		
+		/* if the user is editing from an existing diagram, they have to choose a new name. They're switched *
+		 * directly to the diagram name panel so they have to enter a new name as they would otherwise       *
+		 * not be allowed to proceed.                                                                        */
+		if(diagramToEdit != null)
+			dialog.getWizardComponents().setCurrentIndex(DIAGRAM_NAME);
+		
+		/* when the user clicks on the finish button they'll be prompted with a summary text area dialog     * 
+		 * describing what they have created so far and asking for a confirmation to proceed with the actual *
+		 * creation of the template.                                                                 		 */
+		dialog.getWizardComponents().setFinishAction(new FinishAction(dialog.getWizardComponents()){
+			@Override
+			public void performAction(){
+				String[] options = {
+						resources.getString("dialog.summary.ok_button_label"),
+						resources.getString("dialog.summary.cancel_button_label")};
+				int result = SpeechSummaryPane.showDialog(
+						dialog, 
+						resources.getString("dialog.summary.title"), 
+						model.toString(),
+						SpeechSummaryPane.OK_CANCEL_OPTION,
+						options);
+				
+				if(result == SpeechSummaryPane.CANCEL){ // user wants to continue editing  
+					SoundFactory.getInstance().play(SoundEvent.CANCEL);
+					return;
+				}
+				/* create the actual diagram (it will be return to the client class by execute()) */
+				diagram = createDiagram(model);
+				dialog.dispose();
+			}
+		});
+	}
+	
+	public Wizard(Frame frame, Collection<String> existingDiagrams){
+		this(frame,existingDiagrams,null);
+	}
+	
+	public Diagram execute(){
+		diagram = null;
+		dialog.show();
+		if(diagram == null)
+			SoundFactory.getInstance().play(SoundEvent.CANCEL);
+		return diagram;
+	}
+	
+	@SuppressWarnings("serial")
+	private void initWizardComponents(Collection<String> existingDiagrams, final Diagram diagramToEdit){
+		/* --- MAIN PANEL --- */
+		String[] choices = {
+				resources.getString("panel.home.choice.diagram_name"),
+				resources.getString("panel.home.choice.nodes"),
+				resources.getString("panel.home.choice.edges"),
+		};
+		int[] nexts = {DIAGRAM_NAME,NODES,EDGES};
+		
+		/* panel for the selection of main tasks when creating the diagram: enter diagram name, create node and      *
+		 * create edge. When a name is assigned an item is added to the selection which allows the user to finish    *
+		 * the template creation, much as they would do by pressing the finish button. If the user edits an existing * 
+		 * diagram they're prompted with a message to enter a new diagram name (as there cannot be two diagrams      *
+		 * with the same name. When the user enters the name the message goes away                                   */
+		add(HOME,new SelectWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.home.title.new"),
+				Arrays.asList(choices),
+				nexts,
+				SpeechWizardPanel.DISABLE_SWITCH
+				){
+			@Override
+			public void update(){
+				if(!model.diagramName.value.isEmpty()){
+					dialog.setFinishButtonEnabled(true);
+					/* if the diagram has a name the template creation can finish. So add a selection item to the *
+					 * comboBox unless it's already there from a previous update (item count < 4 )                */
+					if(comboBox.getItemCount() < 4)
+						((DefaultComboBoxModel)comboBox.getModel()).addElement(resources.getString("panel.home.choice.finish"));
+				}
+				super.update();
+			}
+			@Override
+			public void next(){
+				if(comboBox.getSelectedIndex() == 3)
+					dialog.getWizardComponents().getFinishButton().doClick();
+				else
+					super.next();
+			}
+		});
+
+		/* --- DIAGRAM NAME INPUT PANEL --- */
+		add(DIAGRAM_NAME, new TextWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString(diagramToEdit == null ? "panel.diagram_name.title" : "panel.diagram_name.title.editing_existing_diagram"),
+				existingDiagrams, 
+				HOME, 
+				HOME, 
+				model.diagramName	
+			){
+			@Override
+			public void update(){
+				/* this is a little nasty trick to achieve the following: when the user creates a new diagram out of an already existing 
+				 * one they're directly prompted with this panel. We want the name of the diagram to be there for awareness 
+				 * but at the same time it must not be accepted by the program as it would otherwise clash with 
+				 * with the starting diagam's. As the program accepts it when the text entered in the text field is equal to 
+				 * model.diagramName.value (for when the user wants to re-edit the name of a diagram they're creating) we must 
+				 * fill model.DiagramName.value with the name of the starting diagram to get it shown and spoken out but then 
+				 * it's assigned the empty string not to let the user to go forward */
+				if(diagramToEdit != null && model.diagramName.value.isEmpty()){
+					model.diagramName.value = diagramToEdit.getName();
+					super.update();
+					model.diagramName.value = "";
+				}else{
+					super.update();
+				}
+			}
+		});
+		
+		/* --- NODE ACTION SELECTION PANEL --- */
+		/* decide whether to add a new node or to edit/delete an existing node */ 
+		String[] nodeOptions = {
+				resources.getString("panel.nodes.actions.add"),
+				resources.getString("panel.nodes.actions.edit"),
+				resources.getString("panel.nodes.actions.del"),
+				resources.getString("panel.nodes.actions.finish")};
+		int[] nodeNexts = {NODE_TYPE,NODE_EDIT,NODE_DEL,HOME};
+		add(NODES, new ActionChooserPanel(
+				dialog.getWizardComponents(),
+				model.nodes.getNames(),
+				resources.getString("panel.nodes.title"),
+				nodeOptions,
+				nodeNexts,
+				HOME,
+				node
+			));
+		
+		/* --- NODE TYPE NAME INPUT PANEL --- */
+		add(NODE_TYPE,new TextWizardPanel(
+			dialog.getWizardComponents(),
+			resources.getString("panel.node_name.title"),
+			model.nodes.getNames(),
+			NODE_SHAPE,
+			NODES,
+			node.type
+			));
+
+		/* --- NODE TO DELETE SELECTION PANEL*/
+		add(NODE_DEL, new DeletePanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.node_del.title"),
+				NODES,
+				NODES,
+				model.nodes));
+		
+		/* -- NODE TO EDIT SELECTION PANEL */
+		add(NODE_EDIT, new EditPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.node_edit.title"),
+				NODE_TYPE,
+				NODES,
+				model.nodes,
+				node
+			));
+		
+		ShapeType[] shapeTypes = ShapeType.values();
+		ArrayList<String> shapeTypeNames = new ArrayList<String>(shapeTypes.length);
+		for(int i=0; i<shapeTypes.length;i++)
+			if(shapeTypes[i] != ShapeType.Transparent)
+				shapeTypeNames.add(shapeTypes[i].toString());
+		
+		/* -- NODE SHAPE SELECTION PANEL --- */ 
+		add(NODE_SHAPE, new SelectWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.node_shape.title"),
+				shapeTypeNames,
+				NODE_YESNO_PROPERTIES,
+				NODE_TYPE,
+				node.shape
+				));
+		
+		/* --- SELECT WHETHER THE THE NODE HAS TO HAVE PROPERTIES --- */
+		String[] yesnoPropertyOptions = {
+				resources.getString("panel.yesno_properties.add"),
+				resources.getString("panel.yesno_properties.finish")
+		};
+		int[] yesnoPropertyNexts = {PROPERTIES,NODES};
+		
+		add(NODE_YESNO_PROPERTIES,new SelectWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.yesno_properties.title"),
+				Arrays.asList(yesnoPropertyOptions),
+				yesnoPropertyNexts,
+				NODE_SHAPE
+				){
+			@Override
+			public void next(){
+				if(comboBox.getSelectedIndex() == 1){
+					Model.Node newNode = new Model.Node();
+					Model.copy(node, newNode);
+					model.nodes.put(newNode.id,newNode);
+				}
+				super.next();
+			}
+		});
+		
+		/* --- PROPERTIES ACTION SELECTION PANEL --- */
+		String[] propertyOptions = {
+				resources.getString("panel.properties.actions.add"),
+				resources.getString("panel.properties.actions.edit"),
+				resources.getString("panel.properties.actions.del"),
+				resources.getString("panel.properties.action.finish")};
+		int[] propertyNexts = {PROPERTY_TYPE,PROPERTY_EDIT,PROPERTY_DEL,NODES};
+		
+		add(PROPERTIES, new ActionChooserPanel(
+				dialog.getWizardComponents(),
+				node.properties.getNames(),
+				resources.getString("panel.properties.title"),
+				propertyOptions,
+				propertyNexts,
+				NODE_SHAPE,
+				property
+			){
+				@Override
+				public void next(){
+					/* if the user selects finish, create a new node put in it the values of */
+					/* the temporary property and store it in the model                      */  
+					if( (comboBox.getSelectedIndex() == 1 && comboBox.getItemCount() == 2)||
+							(comboBox.getSelectedIndex() == 3 && comboBox.getItemCount() == 4)){ 
+						Model.Node newNode = new Model.Node();
+						Model.copy(node, newNode);
+						model.nodes.put(newNode.id,newNode);
+					}
+					super.next();
+				}
+		});
+		
+		/* --- PROPERTY TYPE NAME INPUT PANEL --- */
+		add(PROPERTY_TYPE,new TextWizardPanel(
+			dialog.getWizardComponents(),
+			resources.getString("panel.property_name.title"),
+			node.properties.getNames(),
+			PROPERTY_POSITION,
+			PROPERTIES,
+			property.type
+			));
+		
+		/* --- PROPERTY TO DELETE SELECTION PANEL --- */
+		add(PROPERTY_DEL, new DeletePanel(
+			dialog.getWizardComponents(),
+			resources.getString("panel.property_del.title"),
+			PROPERTIES,
+			PROPERTIES,
+			node.properties
+			));
+
+		/* --- PROPERTY TO EDIT SELECTION PANEL --- */
+		add(PROPERTY_EDIT, new EditPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.property_edit.title"),
+				PROPERTY_TYPE,
+				PROPERTIES,
+				node.properties,
+				property
+				));
+		
+		/* --- PROPERTY POSITION SELECTION DIALOG --- */
+		SimpleShapeNode.Position positions[] = SimpleShapeNode.Position.values(); 
+		ArrayList<String> positionNames = new ArrayList<String>(positions.length);
+		for(int i=0; i<positions.length;i++)
+			positionNames.add(positions[i].toString());
+		int[] positionNexts = {PROPERTY_YESNO_MODIFIER,PROPERTY_SHAPE};
+		
+		
+		add(PROPERTY_POSITION, new SelectWizardPanel(
+			dialog.getWizardComponents(),
+			resources.getString("panel.property_position.title"),
+			positionNames,
+			positionNexts,
+			PROPERTY_TYPE,
+			property.position
+			));
+		
+		/* --- PROPERTY SHAPE SELECTION DIALOG --- */
+		shapeTypeNames.add(ShapeType.Transparent.toString());
+		add(PROPERTY_SHAPE, new SelectWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.property_shape.title"),
+				shapeTypeNames,
+				PROPERTY_YESNO_MODIFIER,
+				PROPERTY_POSITION,
+				property.shape
+				));
+		
+		/* --- SELECT WHETHER THE THE PROPERTY HAS TO HAVE MODIFIERS --- */
+		String[] yesnoModifierOptions = {
+				resources.getString("panel.yesno_modifiers.add"),
+				resources.getString("panel.yesno_modifiers.finish")
+		};
+		int[] yesnoModifierNexts = {MODIFIERS,PROPERTIES};
+		
+		add(PROPERTY_YESNO_MODIFIER,new SelectWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.yesno_modifiers.title"),
+				Arrays.asList(yesnoModifierOptions),
+				yesnoModifierNexts,
+				PROPERTY_POSITION
+				){
+			@Override
+			public void next(){
+				if(comboBox.getSelectedIndex() == 1){
+					Model.Property newProperty = new Model.Property();
+					Model.copy(property, newProperty);
+					node.properties.put(newProperty.id,newProperty);
+				}
+				super.next();
+			}
+		});
+		/* --- MODIFIERS ACTION SELECTION PANE --- */
+		String[] modifierOptions = {
+				resources.getString("panel.modifiers.actions.add"),
+				resources.getString("panel.modifiers.actions.edit"),
+				resources.getString("panel.modifiers.actions.del"),
+				resources.getString("panel.modifiers.actions.finish")
+		};
+		int[] modifiersNexts = {MODIFIER_TYPE,MODIFIER_EDIT,MODIFIER_DEL,PROPERTIES};
+		
+		add(MODIFIERS, new ActionChooserPanel(
+				dialog.getWizardComponents(),
+				property.modifiers.getNames(), 
+				resources.getString("panel.modifiers.title"),
+				modifierOptions, 
+				modifiersNexts, 
+				PROPERTY_POSITION, 
+				modifier){
+				
+				@Override
+				public void next(){
+					/* if the user selects finish, create a new property put in it the values of */
+					/* the temporary property and store it in the model                          */
+					if( (comboBox.getSelectedIndex() == 1 && comboBox.getItemCount() == 2)||
+							(comboBox.getSelectedIndex() == 3 && comboBox.getItemCount() == 4)){ 
+						Model.Property newProperty = new Model.Property();
+						Model.copy(property, newProperty);
+						node.properties.put(newProperty.id,newProperty);
+					}
+					super.next();
+				}
+		});
+		
+		/* --- MODIFIER TYPE PANEL --- */
+		add(MODIFIER_TYPE, new TextWizardPanel(
+				dialog.getWizardComponents(), 
+				resources.getString("panel.modifier_type.title"), 
+				property.modifiers.getNames(),
+				MODIFIER_FORMAT, 
+				MODIFIERS,
+				modifier.type
+				));
+		
+		add(MODIFIER_DEL, new DeletePanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.modifier_del.title"), 
+				MODIFIERS, 
+				MODIFIERS, 
+				property.modifiers));
+		
+		add(MODIFIER_EDIT, new EditPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.modifier_edit.title"),
+				MODIFIER_TYPE,
+				MODIFIERS,
+				property.modifiers,
+				modifier
+		));
+		
+		add(MODIFIER_FORMAT, new FormatWizardPanel());
+		/* --- EDGE ACTION SELECTION PANEL --- */
+		/* decide whether to add a new edge or to edit/delete an existing edge */ 
+		String[] edgeOptions = {
+				resources.getString("panel.edges.actions.add"),
+				resources.getString("panel.edges.actions.edit"),
+				resources.getString("panel.edges.actions.del"),
+				resources.getString("panel.edges.actions.finish")};
+		int[] edgeNexts = {EDGE_TYPE,EDGE_EDIT,EDGE_DEL,HOME};
+		add(EDGES, new ActionChooserPanel(
+				dialog.getWizardComponents(),
+				model.edges.getNames(),
+				resources.getString("panel.edges.title"),
+				edgeOptions,
+				edgeNexts,
+				HOME,
+				edge
+			));
+		
+		/* --- EDGE TYPE NAME INPUT PANEL --- */
+		add(EDGE_TYPE,new TextWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.edge_name.title"),
+				model.edges.getNames(),
+				EDGE_LINE_STYLE,
+				EDGES,
+				edge.type
+		));
+		
+		/* --- EDGE TO DELETE SELECTION PANEL --- */
+		add(EDGE_DEL, new DeletePanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.edge_del.title"),
+				EDGES,
+				EDGES,
+				model.edges));
+		
+		/* --- EDGE TO EDIT SELECTION PANEL --- */
+		add(EDGE_EDIT, new EditPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.edge_edit.title"),
+				EDGE_TYPE,
+				EDGES,
+				model.edges,
+				edge
+			));
+
+		/* --- LINE STYLE SELECTION PANEL --- */
+		LineStyle[] lineStyles = LineStyle.values();	
+		String[] lineStyleNames = new String[lineStyles.length];
+		for(int i=0; i<lineStyles.length;i++)
+			lineStyleNames[i] = lineStyles[i].toString();
+		                                 
+		add(EDGE_LINE_STYLE, new SelectWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.edge_linestyle.title"),
+				Arrays.asList(lineStyleNames),
+				EDGE_MIN_NODES,
+				EDGE_TYPE,
+				edge.lineStyle
+				));
+		
+		/* --- MIN NODES SELECTION PANEL --- */
+		SpinnerModel minNodesSpinnerModel = new LoopSpinnerNumberModel(2,2,4);
+		add(EDGE_MIN_NODES,new SpinnerWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.edge_min_nodes.title"),
+				minNodesSpinnerModel,
+				EDGE_MAX_NODES,
+				EDGE_LINE_STYLE,
+				edge.minNodes
+				));
+		
+		/* --- MAX NODES SELECTION PANEL --- */
+		SpinnerModel maxNodesSpinnerModel = new LoopSpinnerNumberModel(2,2,4);
+		add(EDGE_MAX_NODES, new SpinnerWizardPanel(
+			dialog.getWizardComponents(),
+			resources.getString("panel.edge_max_nodes.title"),
+			maxNodesSpinnerModel,
+			EDGE_YESNO_ARROW_HEAD,
+			EDGE_MIN_NODES,
+			edge.maxNodes
+			){
+				@Override
+				public void next(){
+					int min = Integer.parseInt(edge.minNodes.value);
+					int max = Integer.parseInt(spinner.getValue().toString());
+					if(min > max){
+						NarratorFactory.getInstance().speak(resources.getString("dialog.error.min_max"));
+					}else{
+						super.next();
+					}
+				}
+				
+		});
+		
+		/* --- SELECT WHETHER THE EDGE MUST HAVE ARROW HEADS OR NOT --- */
+		String[] arrowHeadOptions = {
+				resources.getString("panel.edge_yesno_arrow_head.actions.add"),
+				resources.getString("panel.edge_yesno_arrow_head.actions.finish")
+		};
+		int[] arrowHeadNexts = {EDGE_ARROW_HEAD,EDGES};
+		add(EDGE_YESNO_ARROW_HEAD,new SelectWizardPanel(
+				dialog.getWizardComponents(),
+				resources.getString("panel.edge_yesno_arrow_head.title"),
+				Arrays.asList(arrowHeadOptions),
+				arrowHeadNexts,
+				EDGE_MAX_NODES
+				){
+			@Override
+			public void next(){
+				if(comboBox.getSelectedIndex() == 1){
+					Model.Edge newEdge = new Model.Edge();
+					Model.copy(edge, newEdge);
+					model.edges.put(newEdge.id,newEdge);
+				}
+				super.next();
+			}
+		});
+		
+		/* --- ARROW HEAD SELECTION PANEL --- */
+		add(EDGE_ARROW_HEAD, new ArrowHeadPanel());
+		
+		add(LAST_PANEL, new DummyWizardPanel(dialog.getWizardComponents()));
+		
+		SpeechUtilities.changeTabListener((JComponent)dialog.getContentPane(), dialog);
+	}
+	
+	private void add(int index, JWizardPanel panel){
+		dialog.getWizardComponents().addWizardPanel(index,panel);
+	}
+	
+	private Diagram createDiagram(Model model){
+		/* create the node prototypes */
+		Node[] nodes = new Node[model.nodes.size()];
+		int i = 0;
+		for(Model.Node n : model.nodes.values()){
+			nodes[i] = createDiagramNode(n);
+			i++;
+		}
+		/* create the edge prototypes */
+		Edge[] edges = new Edge[model.edges.size()];
+		i = 0;
+		for(Model.Edge e : model.edges.values()){
+			edges[i] = createDiagramEdge(e);
+			i++;
+		}
+		return Diagram.newInstance(model.diagramName.value, nodes, edges, new SimpleShapePrototypePersistenceDelegate());
+	}
+	
+	private Node createDiagramNode(Model.Node n){
+		/* set up the properties */
+		LinkedHashMap<String,Set<String>> propertiesTypeDefinition = new LinkedHashMap<String,Set<String>>();
+		/* create the property type definition */
+		for(Model.Property modelProperty : n.properties.values()){
+			Set<String> modifiersTypeDefinition = new LinkedHashSet<String>();
+			for(Model.Modifier modifier : modelProperty.modifiers.values())
+				modifiersTypeDefinition.add(modifier.type.value);
+			propertiesTypeDefinition.put(modelProperty.type.value, modifiersTypeDefinition);
+		}
+		NodeProperties properties = new NodeProperties(propertiesTypeDefinition);	
+		/* now that properties object is created attach the views on it */
+		for(Model.Property modelProperty : n.properties.values()){
+			PropertyView propertyView = new PropertyView(
+					SimpleShapeNode.Position.valueOf(modelProperty.position.value),
+					modelProperty.shape.value.isEmpty() ? 
+							/* doesn't really matter as position is inside and shape won't be taken into account */ 
+							SimpleShapeNode.ShapeType.Rectangle :   
+							SimpleShapeNode.ShapeType.valueOf(modelProperty.shape.value) 
+					);
+			properties.setView(modelProperty.type.value, propertyView);
+			/* modifier view */
+			for(Model.Modifier modelModifier : modelProperty.modifiers.values()){
+				boolean bold = false;
+				boolean italic = false;
+				boolean underline = false;
+				String prefix = "";
+				String suffix = "";	
+				for(String value : modelModifier.format.values){
+					if(value.equals(resources.getString("modifier.format.bold"))){
+						bold = true;
+					}else if(value.equals(resources.getString("modifier.format.underline"))){
+						underline = true;
+					}else if(value.equals(resources.getString("modifier.format.italic"))){
+						italic = true;
+					}else if(value.equals(resources.getString("modifier.format.prefix"))){
+						prefix = modelModifier.affix.values[PREFIX_INDEX];
+					}else if(value.equals(resources.getString("modifier.format.suffix"))){
+						suffix = modelModifier.affix.values[SUFFIX_INDEX];
+					}
+				}
+				ModifierView modifierView = new ModifierView(underline,bold,italic,prefix,suffix);
+				properties.getModifiers(modelProperty.type.value).setView(modelModifier.type.value, modifierView);
+			}
+		}
+		return SimpleShapeNode.getInstance(
+				SimpleShapeNode.ShapeType.valueOf(n.shape.value), 
+				n.type.value, 
+				properties);
+	}
+	
+	private Edge createDiagramEdge(Model.Edge e){
+		/* create the arrow head array out of the string stored in the model */
+		ArrowHead[] arrowHeads = new ArrowHead[e.arrowHeads.values.length];
+		for(int i=0; i<e.arrowHeads.values.length;i++){
+			try {
+				arrowHeads[i] = ArrowHead.getArrowHeadFromString(e.arrowHeads.values[i]);
+			} catch (IOException ioe) {
+				throw new RuntimeException(ioe);// the wizard mustn't allow the user to enter different strings
+			}
+		}
+		return new SimpleShapeEdge(
+				e.type.value,
+				LineStyle.valueOf(e.lineStyle.value),
+				arrowHeads,
+				e.arrowHeadsDescriptions.values,
+				Integer.parseInt(e.minNodes.value),
+				Integer.parseInt(e.maxNodes.value)
+				);
+	}
+	
+	private Model createModel(Diagram diagram) {
+		Model model = new Model();
+		if(diagram == null)
+			return model;
+		
+		/* the name isn't copied as the user as to find a new one */
+		/* model.diagramName.value = diagram.getName();*/
+
+		/* nodes */
+		for(Node n : diagram.getNodePrototypes()){
+			if(!(n instanceof SimpleShapeNode))
+				continue;
+			Model.Node modelNode = createModelNode((SimpleShapeNode)n);
+			model.nodes.put(modelNode.id,modelNode);
+		}
+		/* edges */
+		for(Edge e : diagram.getEdgePrototypes()){
+			if(!(e instanceof SimpleShapeEdge))
+				continue;
+			Model.Edge modelEdge = createModelEdge((SimpleShapeEdge)e);
+			model.edges.put(modelEdge.id, modelEdge);
+		}
+		return model;
+	}
+	
+	/**
+	 * fills up the model node object with informations from the real diagram node 
+	 * @param n
+	 * @return
+	 */
+	private Model.Node createModelNode(SimpleShapeNode n){
+		Model.Node modelNode = new Model.Node();
+		modelNode.type.value = n.getType();
+		modelNode.shape.value = n.getShapeType().toString();
+		
+		NodeProperties properties = n.getProperties();
+		for(String propertyType : properties.getTypes()){
+			Model.Property modelProperty = new Model.Property();
+			modelProperty.type.value = propertyType;
+			/* if the view is not a PropertyView or is null then assign a default value         */
+			/* it should never happen but it's just to keep it safer and more forward compliant */ 
+			if(! (properties.getView(propertyType) instanceof PropertyView)){
+				modelProperty.position.value = SimpleShapeNode.Position.Inside.toString();
+			}else{
+				PropertyView propertyView = (PropertyView)properties.getView(propertyType);
+				modelProperty.position.value = propertyView.getPosition().toString();
+				modelProperty.shape.value = propertyView.getShapeType().toString();
+			}
+			Modifiers modifiers = properties.getModifiers(propertyType);
+			for(String modifierType : modifiers.getTypes()){
+				Model.Modifier modelModifier = new Model.Modifier();
+				modelModifier.type.value = modifierType;
+				if(modifiers.getView(modifierType) instanceof ModifierView){
+					ModifierView modifierView = (ModifierView)modifiers.getView(modifierType);
+					/* the string array with the modifier values must be created, so the size must be known before */
+					int numModifierValues = 0;
+					if(modifierView.isBold())
+						numModifierValues++;
+					if(modifierView.isItalic())
+						numModifierValues++;
+					if(modifierView.isUnderline())
+						numModifierValues++;
+					if(!modifierView.getPrefix().isEmpty())
+						numModifierValues++;
+					if(!modifierView.getSuffix().isEmpty())
+						numModifierValues++;
+					/* create the string array and fill it up with values */
+					modelModifier.format.values = new String[numModifierValues];
+					numModifierValues = 0;
+					if(modifierView.isBold())
+						modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.bold");
+					if(modifierView.isItalic())
+						modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.italic");
+					if(modifierView.isUnderline())
+						modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.underline");
+					if(!modifierView.getPrefix().isEmpty()){
+						modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.prefix");
+						modelModifier.affix.values[PREFIX_INDEX] = modifierView.getPrefix(); 
+					}
+							
+					if(!modifierView.getSuffix().isEmpty()){
+						modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.suffix"); 
+						modelModifier.affix.values[SUFFIX_INDEX] = modifierView.getSuffix();
+					}
+				}
+				modelProperty.modifiers.put(modelModifier.id, modelModifier);
+			}
+			modelNode.properties.put(modelProperty.id, modelProperty);
+		}
+		return modelNode;
+	}
+	
+	private Model.Edge createModelEdge(SimpleShapeEdge e){
+		Model.Edge modelEdge = new Model.Edge();
+		modelEdge.type.value = e.getType();
+		modelEdge.lineStyle.value = e.getStyle().toString();
+		modelEdge.maxNodes.value = Integer.toString(e.getMaxAttachedNodes());
+		modelEdge.minNodes.value = Integer.toString(e.getMinAttachedNodes());
+		
+		/* arrow heads and arrowheads descriptions */
+		modelEdge.arrowHeadsDescriptions.values = e.getAvailableEndDescriptions();
+		modelEdge.arrowHeads.values = new String[e.getHeads().length];
+		for(int i  =0; i<e.getHeads().length;i++){
+			modelEdge.arrowHeads.values[i] = e.getHeads()[i].toString();
+		}
+		
+		return modelEdge;
+	}
+	
+	private SpeechWizardDialog dialog;
+	private Diagram diagram;
+	private ResourceBundle resources;
+	private Model model;
+	/* these are the temporary variables where the data are stored during the wizard  *
+	 * when a sub task is completed ( node creation, edge creation, property creation)*  
+	 * the data stored in the temporary variables are saved in the model              */
+	private Model.Node node;
+	private Model.Property property;
+	private Model.Modifier modifier;
+	private Model.Edge edge;
+	
+	static int HOME = 0;
+	static int DIAGRAM_NAME = 1;
+	static int NODES = 2;
+	static int NODE_TYPE = 3;
+	static int NODE_DEL = 4;
+	static int NODE_EDIT = 5;
+	static int NODE_SHAPE = 6;
+	static int NODE_YESNO_PROPERTIES = 7;
+	static int PROPERTIES = 8;
+	static int PROPERTY_TYPE = 9;
+	static int PROPERTY_DEL = 10;
+	static int PROPERTY_EDIT = 11; 
+	static int PROPERTY_POSITION = 12;
+	static int PROPERTY_SHAPE = 13;
+	static int PROPERTY_YESNO_MODIFIER = 14;
+	static int MODIFIERS = 15;
+	static int MODIFIER_TYPE = 16;
+	static int MODIFIER_DEL = 17;
+	static int MODIFIER_EDIT = 18;
+	static int MODIFIER_FORMAT = 19;
+	static int EDGES = 20;
+	static int EDGE_TYPE = 21; 
+	static int EDGE_DEL = 22; 
+	static int EDGE_EDIT = 23;
+	static int EDGE_LINE_STYLE = 24;
+	static int EDGE_MIN_NODES = 25;
+	static int EDGE_MAX_NODES = 26;
+	static int EDGE_YESNO_ARROW_HEAD = 27;
+	static int EDGE_ARROW_HEAD = 28;
+	static int LAST_PANEL = 29;
+	
+	private static int PREFIX_INDEX = 0;
+	private static int SUFFIX_INDEX = 1;
+	
+	/* the abstract class from which the panels for Nodes, edges and Modifiers inherit 
+	 * It displays the actions (add,edit,delete,finish) on a comboBox. if elementNames is empty 
+	 * it means that no element has been created yet and therefore edit and delete actions are disabled 
+	 */
+	@SuppressWarnings("serial")
+	private static class ActionChooserPanel extends SpeechWizardPanel{
+		ActionChooserPanel(JWizardComponents wizardComponents,Collection<String> elementNames, String title, String[] options, int[] nexts, int previous, Model.Element temporaryElement){
+			super(wizardComponents,title,OWN_SWITCH, previous);
+			this.options = options;
+			comboBoxModel = new DefaultComboBoxModel();
+			comboBoxModel.addElement(options[0]);
+			comboBoxModel.addElement(options[3]);
+			comboBox = new LoopComboBox(comboBoxModel);
+			comboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
+			layoutComponents(comboBox);
+			this.elementNames = elementNames;
+			this.temporaryElement = temporaryElement;
+			this.nexts = nexts;
+		}
+
+		@Override
+		public void update(){
+			if(elementNames.isEmpty() && comboBoxModel.getSize() == 4){
+				comboBoxModel.removeElement(options[1]);
+				comboBoxModel.removeElement(options[2]);
+			}else if(!elementNames.isEmpty() && comboBoxModel.getSize() == 2){
+				comboBoxModel.insertElementAt(options[1],1);
+				comboBoxModel.insertElementAt(options[2],2);
+			}
+			super.update();
+		}
+		
+		@Override
+		public void next(){
+			/* if the selection was add element, then we clear the temporary holder */
+			if(comboBox.getSelectedIndex() == 0)
+				temporaryElement.clear();
+			/* jump to the selected next step, works both when it's only add/finish and when it's add/delete/edit/finish */
+			for(int i=0; i<options.length; i++)
+				if(comboBox.getSelectedItem().equals(options[i])){
+					switchPanel(nexts[i]);
+					return;
+				}
+		}
+		
+		JComboBox comboBox;
+		Collection<String> elementNames;
+		DefaultComboBoxModel comboBoxModel;
+		String[] options;
+		Model.Element temporaryElement;
+		int[] nexts;
+	}
+	
+	@SuppressWarnings("serial")
+	private static class DeletePanel extends SpeechWizardPanel {
+		DeletePanel(JWizardComponents wizardComponents,String title, int next, int previous, ModelMap<? extends Model.Element> elements){
+			super(wizardComponents,title,next, previous);
+			this.elements = elements;
+			comboBox = new LoopComboBox();
+			comboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener());
+			layoutComponents(comboBox);
+		}
+		
+		@Override
+		public void update(){
+			String[] options = new String[elements.values().size()];
+			options = elements.getNames().toArray(options);
+			comboBox.setModel(new DefaultComboBoxModel(options));
+			super.update();
+		}
+		
+		/**
+		 * the default behaviour is to delete the selected element 
+		 */
+		@Override
+		public void next(){
+			Model.Element elementToDelete = null;
+			for(Model.Element element : elements.values()){
+				if(element.type.value.equals(comboBox.getSelectedItem())){
+					elementToDelete = element;
+					break;
+				}
+			}
+			Object o = elements.remove(elementToDelete.id);
+			assert(o != null);
+			super.next();
+		}
+		
+		JComboBox comboBox;
+		ModelMap<? extends Model.Element> elements;
+	}
+	
+	@SuppressWarnings("serial")
+	private static class EditPanel extends DeletePanel {
+		EditPanel(JWizardComponents wizardComponents,
+				String title, 
+				int next, 
+				int previous, 
+				ModelMap<? extends Model.Element> elements,
+				Model.Element temporaryHolder){
+			super(wizardComponents, title,next, previous,elements);
+			this.temporaryHolder = temporaryHolder;
+			this.next = next;
+		}
+		
+		@Override
+		public void next(){
+			Model.Element selected = null;
+			for(Model.Element e : elements.values()){
+				if(e.type.value.equals(comboBox.getSelectedItem())){
+					selected = e;
+					break;
+				}
+			}
+			
+			Model.copy(selected, temporaryHolder);
+			switchPanel(next);
+		}
+		
+		int next;
+		Model.Element temporaryHolder;
+	}
+	
+	@SuppressWarnings("serial")
+	private class FormatWizardPanel extends SpeechWizardPanel{
+		FormatWizardPanel(){
+			super(dialog.getWizardComponents(),resources.getString("panel.modifier_format.title"),MODIFIERS,MODIFIER_TYPE);
+			String values[] = {
+					resources.getString("modifier.format.bold"),
+					resources.getString("modifier.format.underline"),
+					resources.getString("modifier.format.italic"),
+					resources.getString("modifier.format.prefix"),
+					resources.getString("modifier.format.suffix"),
+			};
+
+			checkBoxes = new JCheckBox[values.length];
+			checkBoxPanel = new JPanel(new GridLayout(0, 1));
+			for(int i=0; i<values.length;i++){
+				String value = values[i];
+				checkBoxes[i] = new JCheckBox(value);
+				checkBoxes[i].getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
+				checkBoxes[i].getActionMap().put("enter", new AbstractAction(){
+					@Override
+					public void actionPerformed(ActionEvent arg0) {
+						getWizardComponents().getNextButton().doClick();
+					}
+				});
+				checkBoxes[i].addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener());
+				/* prefix and suffix check boxes must have a JText area for the user to enter the String */
+				if(i == 3 || i == 4){
+					JPanel panel = new JPanel();
+					panel.setLayout(new FlowLayout(FlowLayout.LEFT,0,0));
+					panel.add(checkBoxes[i]);
+					if(i == 3){
+						prefixTextField = new JTextField();
+						prefixTextField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
+						prefixTextField.getActionMap().put("enter", new AbstractAction(){
+							@Override
+							public void actionPerformed(ActionEvent arg0) {
+								getWizardComponents().getNextButton().doClick();
+							}
+						});
+						prefixTextField.getAccessibleContext().setAccessibleName(checkBoxes[i].getText());
+						prefixTextField.setColumns(5);
+						prefixTextField.addKeyListener(SpeechUtilities.getSpeechKeyListener(true));
+						panel.add(prefixTextField);
+					}else{
+						suffixTextField = new JTextField();
+						suffixTextField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
+						suffixTextField.getActionMap().put("enter", new AbstractAction(){
+							@Override
+							public void actionPerformed(ActionEvent arg0) {
+								getWizardComponents().getNextButton().doClick();
+							}
+						});
+						suffixTextField.getAccessibleContext().setAccessibleName(checkBoxes[i].getText());
+						suffixTextField.setColumns(5);
+						suffixTextField.addKeyListener(SpeechUtilities.getSpeechKeyListener(true));
+						panel.add(suffixTextField);
+					}
+					checkBoxPanel.add(panel);
+				}else{
+					checkBoxPanel.add(checkBoxes[i]);
+				}
+			}
+			JScrollPane scrollPane = new JScrollPane(checkBoxPanel); 
+			scrollPane.setFocusable(false);
+			layoutComponents(scrollPane);
+			dialog.getWizardComponents().getFinishButton().setEnabled(true);
+		}
+		
+		/* store the checks into the StrArrayRecord, it doesn't call super.next thus */
+		/* sub classes have to implement call the switch panel on their own          */
+		public void next(){
+			int numCheckedBoxes = 0;
+			for(JCheckBox check : checkBoxes){
+				if(check.isSelected())
+					numCheckedBoxes++;
+			}
+			String[] result = new String[numCheckedBoxes];
+			numCheckedBoxes = 0;
+			for(int i=0; i<checkBoxes.length;i++){
+				/* store the text value of the check boxes, if it's the prefix or suffix */
+				/* append the text entered by the user in the text areas                  */
+				if(checkBoxes[i].isSelected()){
+					String text = checkBoxes[i].getText();
+					if(i == 3)
+						modifier.affix.values[PREFIX_INDEX] = prefixTextField.getText();
+					else if(i == 4)
+						modifier.affix.values[SUFFIX_INDEX] = suffixTextField.getText();
+					result[numCheckedBoxes++] = text;
+				}
+			}
+			modifier.format.values = result;
+			Model.Modifier newModifier = new Model.Modifier();
+			Model.copy(modifier,newModifier);
+			property.modifiers.put(newModifier.id,newModifier);
+			super.next();
+		}
+		
+		@Override
+		public void update(){
+			/* set the check boxes and text field according to the modifier.format. so if we are editing an existing *
+			 * modifier we find the old values, else if it's a new modifier we find everything blank                 */
+			if(modifier.format != null){
+				prefixTextField.setText("");
+				suffixTextField.setText("");
+				for(JCheckBox check : checkBoxes){
+					/* temporarily remove the speech Item listener in order to avoid bla bla bla not triggered by user */
+					check.removeItemListener(SpeechUtilities.getCheckBoxSpeechItemListener());
+					check.setSelected(false);
+					for(String checkedValue : modifier.format.values){
+						if(checkedValue.equals(check.getText())){//for bold,italic,underline
+							check.setSelected(true);
+							if(checkedValue.equals(resources.getString("modifier.format.prefix"))){
+								prefixTextField.setText(modifier.affix.values[PREFIX_INDEX]);
+							}else if(checkedValue.equals(resources.getString("modifier.format.suffix"))){
+								suffixTextField.setText(modifier.affix.values[SUFFIX_INDEX]);
+							}
+							break;
+						}
+					}
+					check.addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener());
+				}
+			}
+			super.update();
+		}
+		
+		@Override
+		protected Component assignFocus(){
+			/* focus on the first item */
+			checkBoxes[0].requestFocus();
+			return checkBoxes[0];
+		}
+		
+		JTextField prefixTextField;
+		JTextField suffixTextField;
+		JPanel checkBoxPanel;
+		JCheckBox checkBoxes[];
+	}
+	
+	@SuppressWarnings("serial")
+	private class ArrowHeadPanel extends SpeechWizardPanel {
+		ArrowHeadPanel(){
+			super(dialog.getWizardComponents(),resources.getString("panel.edge_arrow_head.title"),EDGES,EDGE_YESNO_ARROW_HEAD);
+			JPanel panel = new JPanel(new GridBagLayout());
+			final JScrollPane scrollPane = new JScrollPane(panel);
+			scrollPane.setFocusable(false);
+			GridBagUtilities gridBagUtils = new GridBagUtilities(); 
+			int numArrowHeads = ArrowHead.values().length;
+			arrowsCheckBoxes = new JCheckBox[numArrowHeads];
+			arrowsTextDescriptions = new JTextField[numArrowHeads];
+			for(int i=0; i<numArrowHeads; i++){
+				/* set up the key bindings for all the check boxes and text fields */
+				/* by pressing enter the wizard switches the next panel            */
+				arrowsCheckBoxes[i] = new JCheckBox(ArrowHead.values()[i].toString());
+				arrowsCheckBoxes[i].getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
+				arrowsCheckBoxes[i].getActionMap().put("enter", new AbstractAction(){
+					@Override
+					public void actionPerformed(ActionEvent arg0) {
+						getWizardComponents().getNextButton().doClick();
+					}
+				});
+				arrowsTextDescriptions[i] = new JTextField();
+				arrowsTextDescriptions[i].getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
+				arrowsTextDescriptions[i].getActionMap().put("enter", new AbstractAction(){
+					@Override
+					public void actionPerformed(ActionEvent arg0) {
+						getWizardComponents().getNextButton().doClick();
+					}
+				});
+				/* add the speech to the check boxes */
+				arrowsCheckBoxes[i].addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener());
+				
+				arrowsTextDescriptions[i].setPreferredSize(new Dimension(TEXTFIELD_SIZE, arrowsTextDescriptions[i].getPreferredSize().height));
+				arrowsTextDescriptions[i].getAccessibleContext().setAccessibleName(arrowsCheckBoxes[i].getText());
+				arrowsTextDescriptions[i].addKeyListener(SpeechUtilities.getSpeechKeyListener(true));
+				panel.add(arrowsCheckBoxes[i], gridBagUtils.label());
+				panel.add(arrowsTextDescriptions[i],gridBagUtils.field());
+			}
+			layoutComponents(scrollPane);
+		}
+		
+		@Override
+		public void update(){
+			/* restore the values (checkbox + text) currently in edge.arrowHeads into the panel components */
+			if(edge.arrowHeads != null){
+				for(int i=0; i<arrowsCheckBoxes.length;i++){
+					arrowsCheckBoxes[i].setSelected(false);
+					arrowsTextDescriptions[i].setText("");
+					for(int j=0; j< edge.arrowHeads.values.length; j++){
+						if(arrowsCheckBoxes[i].getText().equals(edge.arrowHeads.values[j])){
+							arrowsCheckBoxes[i].setSelected(true);
+							arrowsTextDescriptions[i].setText(edge.arrowHeadsDescriptions.values[j]);
+							break;
+						}
+					}
+				}
+			}
+			super.update();
+		}
+		@Override
+		public void next(){
+			/* check that the user has entered a text for all of the selected check boxes */
+			int numChecked = 0;//this is to keep count of the checked boxes, used after the check 
+			for(int i=0; i<arrowsCheckBoxes.length;i++){
+				JCheckBox checkBox = arrowsCheckBoxes[i];
+				if(checkBox.isSelected()){
+					numChecked++;
+					/* there cannot be a checked check box without the related textField filled in */
+					if(arrowsTextDescriptions[i].getText().trim().isEmpty()){
+						NarratorFactory.getInstance().speak(
+								MessageFormat.format(
+										resources.getString("dialog.error.empty_desc"),
+										checkBox.getText())
+						);		
+						return;
+					}
+				}
+			}
+			/* copy the label of the checked boxes and the text of the JTextField into the edge fields */
+			edge.arrowHeads.values = new String[numChecked];
+			edge.arrowHeadsDescriptions.values = new String[numChecked];
+			numChecked = 0;
+			for(int i=0; i<arrowsCheckBoxes.length;i++){
+				if(arrowsCheckBoxes[i].isSelected()){
+					edge.arrowHeads.values[numChecked] = arrowsCheckBoxes[i].getText();
+					edge.arrowHeadsDescriptions.values[numChecked] = arrowsTextDescriptions[i].getText().trim();
+					numChecked++;
+				}
+			}
+			/* put the edge (copy of) into the model */
+			Model.Edge newEdge = new Model.Edge();
+			Model.copy(edge, newEdge);
+			model.edges.put(newEdge.id,newEdge);
+			super.next();
+		}
+		
+		@Override
+		protected Component assignFocus(){
+			/* focus on the first item */
+			arrowsCheckBoxes[0].requestFocus();
+			return arrowsCheckBoxes[0];
+		}
+		
+		JCheckBox arrowsCheckBoxes[];
+		JTextField arrowsTextDescriptions[];
+		final int TEXTFIELD_SIZE = 100;
+	}
+}
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Circle.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Ellipse.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Rectangle.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Square.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/Triangle.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/dashedLine.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/dottedLine.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/audio/straightLine.mp3 has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/AudioResourcesService.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,74 @@
+/*  
+ 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.sound;
+
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+/**
+ * This class holds the stream of audio files associated to each 
+ * sound event type. The audio file is played out each time the event, associated to
+ * it, happens.
+ */
+
+public class AudioResourcesService  {
+	/**
+	 * @param type A sound event type 
+	 * @return the file name associated to a sound event type
+	 */
+	public static InputStream getAudiofile(SoundEvent type){
+		if(audioFileNames == null)
+			audioFileNames = new AudioResourcesService();
+		return audioFileNames.nameMap.get(type);
+	}
+	
+	public static Set<SoundEvent> eventTypes(){
+		if(audioFileNames == null)
+			audioFileNames = new AudioResourcesService();
+		return audioFileNames.nameMap.keySet();
+	}
+	
+	private AudioResourcesService(){
+		Class<AudioResourcesService> c = AudioResourcesService.class;
+		nameMap = new LinkedHashMap<SoundEvent, InputStream>();
+		nameMap.put(SoundEvent.TREE_NODE_COLLAPSE, c.getResourceAsStream("audio/collapse.mp3"));
+		nameMap.put(SoundEvent.TREE_NODE_EXPAND, c.getResourceAsStream("audio/expand.mp3"));
+		nameMap.put(SoundEvent.LIST_BOTTOM_REACHED, c.getResourceAsStream("audio/endoflist.mp3"));
+		nameMap.put(SoundEvent.LIST_TOP_REACHED, c.getResourceAsStream("audio/endoflist.mp3"));
+		nameMap.put(SoundEvent.JUMP,c.getResourceAsStream("audio/jump.mp3"));
+		nameMap.put(SoundEvent.ERROR,c.getResourceAsStream("audio/error.mp3"));
+		nameMap.put(SoundEvent.OK,c.getResourceAsStream("audio/Ok.mp3"));
+		nameMap.put(SoundEvent.CANCEL,c.getResourceAsStream("audio/cancel.mp3"));
+		nameMap.put(SoundEvent.MESSAGE_OK,c.getResourceAsStream("audio/cancel.mp3"));
+		nameMap.put(SoundEvent.EMPTY,c.getResourceAsStream("audio/cancel.mp3"));
+		nameMap.put(SoundEvent.EDITING, c.getResourceAsStream("audio/editingMode.mp3"));
+		nameMap.put(SoundEvent.MAGNET_ON, c.getResourceAsStream("audio/magnetON.mp3"));
+		nameMap.put(SoundEvent.MAGNET_OFF, c.getResourceAsStream("audio/magnetOFF.mp3"));
+		nameMap.put(SoundEvent.HOOK_ON,c.getResourceAsStream("audio/hookON.mp3"));
+		nameMap.put(SoundEvent.HOOK_OFF,c.getResourceAsStream("audio/hookOFF.mp3"));
+		nameMap.put(SoundEvent.DRAG, c.getResourceAsStream("audio/drag.mp3"));
+	}
+	
+	private LinkedHashMap<SoundEvent, InputStream> nameMap;
+	private static AudioResourcesService audioFileNames;
+	public static String FOLDER = "audio/";
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/BeadsSound.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,187 @@
+/*  
+ 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.sound;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.beadsproject.beads.core.AudioContext;
+import net.beadsproject.beads.core.Bead;
+import net.beadsproject.beads.core.UGen;
+import net.beadsproject.beads.data.Sample;
+import net.beadsproject.beads.data.SampleManager;
+import net.beadsproject.beads.ugens.Gain;
+import net.beadsproject.beads.ugens.SamplePlayer;
+import net.beadsproject.beads.ugens.SamplePlayer.LoopType;
+
+/**
+ * The Sound interface implementation using the Beads library.
+ * For more info abou the library see http://www.beadsproject.net/.
+ */
+class BeadsSound implements Sound {
+	
+	public BeadsSound(){
+		ac = new AudioContext();
+		playerListeners = new HashMap<SoundEvent,PlayerListener>();
+		loopPlayers = new HashMap<SoundEvent,UGen>();
+		
+		/* pre load all the sample to avoid future overhead */
+		for(SoundEvent key : AudioResourcesService.eventTypes()){
+			SampleManager.sample(AudioResourcesService.getAudiofile(key));
+		}
+		ac.start();
+	}
+	
+
+	public void play(InputStream sound, final PlayerListener playerListener) {
+		if(mute)
+			return;
+		SamplePlayer player;
+		Sample sample = null; 
+		if(sound != null)
+			sample = SampleManager.sample(sound);
+		if(sample == null){
+			/* we got problems retrieving the sample to play 
+			*  call the playerListener method and return */
+			if(playerListener != null)
+				playerListener.playEnded();
+			return;
+		}
+		player = new SamplePlayer(ac,sample);
+		player.setKillOnEnd(true);
+		final Gain g = new Gain(ac,1,MASTER_VOLUME); 
+		g.addInput(player);
+		
+		Bead killBill; 
+		if(playerListener != null){
+			killBill = new Bead(){
+				@Override
+				protected void messageReceived(Bead message){
+					playerListener.playEnded();
+					g.kill();
+					playingBead = null;
+				}
+			};
+		}else{
+			killBill = new Bead(){
+				@Override
+				protected void messageReceived(Bead message){
+					g.kill();
+					playingBead = null;
+				}
+			};
+		}
+		
+		player.setKillListener(killBill);
+		playingBead = g;
+		ac.out.addInput(g);
+	}
+
+	public void play(InputStream sound){
+		play(sound, null);
+	}
+	
+	@Override
+	public void play(final SoundEvent evt ){
+		if(evt == null){
+			InputStream s = null;
+			play(s);
+		}else
+			play(evt,playerListeners.get(evt));
+	}
+	
+	public void play(SoundEvent evt, PlayerListener playerListener){
+		if(evt == null){
+			InputStream s = null;
+			play(s,playerListener);
+		}else
+			play(AudioResourcesService.getAudiofile(evt),playerListener);
+	}
+	
+	public void stop(){
+		if(mute)
+			return;
+		if(playingBead != null){
+			playingBead.setKillListener(null);
+			playingBead.kill();
+		}
+	}
+	
+	public void loadSound(InputStream sound){
+		SampleManager.sample(sound);
+	}
+	
+	@Override
+	public void startLoop(SoundEvent action) {
+		if(mute)
+			return;
+		Sample sample = null; 
+		if(action != null){
+			InputStream samplePath = AudioResourcesService.getAudiofile(action);
+			if(samplePath != null)
+				sample = SampleManager.sample(samplePath);
+		}
+		if(sample == null)
+			return;
+		SamplePlayer player = new SamplePlayer(ac,sample);
+		player.setLoopType(LoopType.LOOP_FORWARDS);
+		Gain g = new Gain(ac,1,MASTER_VOLUME); 
+		g.addInput(player);
+		ac.out.addInput(g);
+		loopPlayers.put(action, g);
+	}
+
+	@Override
+	public void stopLoop(SoundEvent action) {
+		UGen g = loopPlayers.get(action);
+		if(g != null){
+			g.kill();
+			loopPlayers.remove(action);
+		}
+	}
+
+	@Override
+	public void setPlayerListener(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;
+	}
+	
+	@Override
+	public void dispose(){
+		ac.stop();
+	}
+	
+	private AudioContext ac;	
+	private Bead playingBead;
+	private Map<SoundEvent,PlayerListener> playerListeners;
+	private Map<SoundEvent,UGen> loopPlayers;
+	private static final float MASTER_VOLUME = 0.35f;
+	private boolean mute;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/PlayerListener.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,30 @@
+/*  
+ 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.sound;
+
+/**
+ * {@code PlayerListeners} can be registered to an object implementing the
+ * {@code Sound} interface. Each time a sound is played registered listeners
+ * will be triggered just after the sound is over. 
+ *
+ */
+public interface PlayerListener {
+	void playEnded();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/Sound.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,59 @@
+/*  
+ 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.sound;
+
+import java.io.InputStream;
+
+/**
+ * An object implementing the {@code Sound} interface can be used to play sound 
+ * samples either just once or in a continuous loop. The client class needs to provide 
+ * a reference to an {@code InputStream} to the sample source.
+ * Furthermore, prebuilt sounds
+ * associated to the events defined by the {@code SoundEvent} enumeration can be played.
+ *
+ */
+public interface Sound {
+
+	public void play(SoundEvent evt);
+
+	public void play(SoundEvent evt, PlayerListener listener);
+
+	public void play(InputStream sound);
+
+	public void play(InputStream sound, PlayerListener listener);
+	
+	public void stop();
+	
+	public void setMuted(boolean mute);
+
+	public void loadSound(InputStream sound);
+
+	public void startLoop(SoundEvent evt);
+
+	public void stopLoop(SoundEvent evt);
+
+	public void setPlayerListener(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/sound/SoundEvent.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,45 @@
+/*  
+ 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.sound;
+
+/**
+ * The events for which the {@code Sound} library provides predefined sounds. 
+ * The sound files sources for each SoundEvent can be retrieved through the {@code AudioResourcesService}
+ * class.    
+ *
+ */
+public enum SoundEvent {
+	ERROR,
+	TREE_NODE_COLLAPSE,
+	TREE_NODE_EXPAND,
+	LIST_TOP_REACHED,
+	LIST_BOTTOM_REACHED,
+	JUMP,
+	OK,
+	CANCEL,
+	EMPTY,
+	EDITING,
+	MAGNET_ON,
+	MAGNET_OFF,
+	HOOK_ON,
+	HOOK_OFF,
+	DRAG,
+	MESSAGE_OK
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/SoundFactory.java	Fri Dec 16 17:35:51 2011 +0000
@@ -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.sound;
+
+/**
+ * The factory class to create {@code Sound} instances. 
+ *
+ */
+public abstract class SoundFactory {
+	
+	public static Sound createInstance(){
+		sound = new BeadsSound();
+		return sound;
+	}
+	
+	public static Sound getInstance(){
+		if(sound == null)
+			throw new IllegalStateException("createInstance() must be called before any getInstance() call");
+		return sound;
+	}
+	
+	private static Sound sound;
+}
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/Ok.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/cancel.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/collapse.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/drag.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/editingMode.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/endoflist.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/error.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/expand.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/hookOFF.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/hookON.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/jump.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/magnetOFF.mp3 has changed
Binary file java/src/uk/ac/qmul/eecs/ccmi/sound/audio/magnetON.mp3 has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/DummyNarrator.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,52 @@
+/*  
+ 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;
+
+/*
+* A dummy implementation of the Narrator interface. All its methods are empty,
+* so every call will have no effect whatsoever.
+*/
+class DummyNarrator implements Narrator {
+
+	@Override
+	public void init() throws NarratorException {}
+	
+	@Override
+	public void setMuted(boolean muted) {}
+
+	@Override
+	public void shutUp() {}
+
+	@Override
+	public void speak(String text) {}
+
+	@Override
+	public void speakWholeText(String text){}
+	
+	@Override
+	public void dispose() {}
+
+	@Override
+	public void setRate(int rate) {	}
+
+	@Override
+	public int getRate() {return 0;}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,47 @@
+/*  
+ 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;
+
+/**
+ *
+ * The {@code Narrator} interface provides high level methods to make use of text to speech synthesis.  
+ *
+ */
+public interface Narrator {
+
+	public void init() throws NarratorException;
+	
+	public void setMuted(boolean muted);
+
+	public void setRate(int rate);
+	
+	public int getRate();
+		
+	public void shutUp();
+
+	public void speak(String text);
+	
+	public void speakWholeText(String text);
+
+	public void dispose();
+
+	int MIN_RATE = 0;
+	int MAX_RATE = 20;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/Narrator.properties	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,29 @@
+component.button=button 
+component.text_field=text field 
+component.text_area=text Area 
+component.combo_box=selected 
+component.spinner=number box 
+component.chech=checked 
+component.uncheck=unchecked 
+
+char.back_space=back space
+char.at=at
+char.new_line=new line
+char.dot=dot
+char.comma=comma
+char.colon=colon
+char.semi_colon=semi colon
+char.lower_than=lower than
+char.greater_than=greater than
+char.delete=delete
+char.sharp=number sign
+char.tilde=tilde
+char.slash=slash
+char.plus=plus
+char.dash=dash
+char.underscore=underscore
+char.space=space
+
+error.no_speech=Could not create the speech synthesizer
+
+ccmi_spell=dot c c m i
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/NarratorException.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,38 @@
+/*  
+ 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.util.ResourceBundle;
+
+/**
+ * A {@code NarratorException} is thrown when a text to speech synthesizer cannot be instantiated
+ * for any reason.
+ *
+ */
+@SuppressWarnings("serial")
+public class NarratorException extends Exception {
+	public NarratorException(String msg){
+		super(msg);
+	}
+	
+	public NarratorException(){
+		super(ResourceBundle.getBundle(Narrator.class.getName()).getString("error.no_speech"));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/NarratorFactory.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,56 @@
+/*  
+ 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.util.logging.Logger;
+
+import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
+
+/**
+ * The factory class to create {@code Narrator} instances. 
+ *
+ */
+public abstract class NarratorFactory {
+	
+	public static Narrator createInstance(){
+		if(singleNarrator == null){
+			if(OsDetector.isWindows()){ 
+				try{
+					singleNarrator = new NativeNarrator();
+					singleNarrator.init();
+				}catch(NarratorException ne){
+					singleNarrator = new DummyNarrator();
+					Logger.getLogger("general").warning("Could not enable text to speech synthesis");
+				}
+			}else{
+				singleNarrator = new DummyNarrator();
+			}
+		}
+		return singleNarrator;
+	}
+	
+	public static Narrator getInstance(){
+		if(singleNarrator == null)
+			throw new IllegalStateException("createInstance() must be called before any getInstance() call");
+		return singleNarrator;
+	}
+	
+	private static Narrator singleNarrator;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,185 @@
+/*  
+ 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.net.URL;
+import java.util.ResourceBundle;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import uk.ac.qmul.eecs.ccmi.utils.NativeLibFileWriter;
+import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
+import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
+
+/*
+ * 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");
+				String path = fileWriter.getFilePath();
+				if(path != null)
+					try{
+						System.load( path );
+						nativeLibraryNotFound = false;
+					}catch(UnsatisfiedLinkError e){
+						/* do nothing: nativeLibraryNotFound won't be set to false */
+						/* which will trigger a NarratorException                  */ 
+					}
+			}
+		}
+	}
+
+	public NativeNarrator(){
+		resources = ResourceBundle.getBundle(Narrator.class.getName());
+	}
+	
+	@Override
+	public void init() throws NarratorException {
+		if(nativeLibraryNotFound)
+			throw new NarratorException();
+		
+		muted = false;
+		queue = new LinkedBlockingQueue<QueueEntry>();
+		executor = new Executor();
+		boolean success = _init();
+		if(!success)
+			throw new NarratorException();
+		rate = Integer.parseInt(PreferencesService.getInstance().get("speech_rate", DEFAULT_RATE_VALUE));
+		_setRate(rate);
+		executor.start();
+	}
+
+	@Override
+	public void setMuted(boolean muted) {
+		this.muted = muted;	
+	}
+	
+	@Override
+	public void setRate(int rate){
+		if(rate < MIN_RATE || rate > MAX_RATE)
+			throw new IllegalArgumentException("Rate value must be between 0 and 20");
+		_setRate(rate);
+		this.rate = rate;
+		PreferencesService.getInstance().put("speech_rate", Integer.toString(rate));
+	}
+	
+	@Override
+	public int getRate(){
+		return rate;
+	}
+
+	@Override
+	public void shutUp() {
+		_shutUp();
+	}
+
+	@Override
+	public void speak(String text) {
+		if(muted)
+			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,false));
+	}
+
+	@Override
+	public void speakWholeText(String text) {
+		if(muted)
+			return;
+		queue.add(new QueueEntry(text,true));
+	}
+
+	@Override
+	public void dispose() {
+		executor.mustSayGoodbye = true;
+		executor.interrupt();
+	}
+	
+	/* native routines used by the Executor thread */
+	private native boolean _init();
+	
+	private native void _dispose();
+	
+	private native void _speak(String text);
+	
+	private native void _speakWholeText(String text);
+	
+	private native void _setRate(int rate);
+	
+	private native void _shutUp();
+	
+	private boolean muted;
+	private int rate;
+	private  LinkedBlockingQueue<QueueEntry> queue;
+	private Executor executor;
+	private ResourceBundle resources;
+	private static String DEFAULT_RATE_VALUE = "13";
+	private static boolean nativeLibraryNotFound;
+	
+	private class Executor extends Thread{
+		private Executor(){
+			super("Narrator Thread");
+			mustSayGoodbye = false;
+		}
+		
+		@Override
+		public void run(){
+			QueueEntry entry;
+			while(!mustSayGoodbye){
+				try {
+					entry = queue.take();
+				} catch (InterruptedException e) {
+					continue; /* start over the while cycle */
+				}
+				if(!entry.speakToEnd && queue.peek() != null)
+					continue;/* the user submitted another text to be spoken out and this can be overwritten */
+				if(entry.speakToEnd){
+					_speakWholeText(entry.text);
+				}else{
+					_speak(entry.text);
+				}
+			}
+			_dispose();
+		}
+		
+		private volatile boolean mustSayGoodbye;
+	}
+	
+	private static class QueueEntry {
+		QueueEntry(String text, boolean speakToEnd){
+			this.text = text;
+			this.speakToEnd = speakToEnd;
+		}
+		String text;
+		boolean speakToEnd;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,374 @@
+/*  
+ 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.AWTKeyStroke;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.FocusTraversalPolicy;
+import java.awt.KeyboardFocusManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JSpinner;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+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;
+
+/**
+ * A class providing static utilities methods concerning the text to speech synthesis. 
+ *
+ */
+public abstract class SpeechUtilities {
+	/* this class is of static use only */
+	private SpeechUtilities(){}
+	
+	public static String getComponentSpeech(Component c){
+		StringBuilder b = new StringBuilder();
+		if(c.getAccessibleContext().getAccessibleName() != null)
+			b.append(c.getAccessibleContext().getAccessibleName());
+		if(c instanceof JButton)
+			b.append(' ').append(resources.getString("component.button"));
+		else if(c instanceof JTextField){
+			b.append(' ').append(resources.getString("component.text_field"));
+			b.append(((JTextField)c).getText());
+		}else if(c instanceof JTextArea){
+			b.append(' ').append(resources.getString("component.text_area"));
+			b.append(((JTextArea)c).getText());
+		}else if(c instanceof JComboBox){
+			b.append(((JComboBox)c).getSelectedItem().toString());
+			b.append(' ').append(resources.getString("component.combo_box"));
+		}else if(c instanceof JCheckBox){
+			b.append(' ').append(((JCheckBox)c).isSelected() ? resources.getString("component.chech") : resources.getString("component.uncheck"));
+		}else if(c instanceof JSpinner){
+			b.append(' ').append(resources.getString("component.spinner"));
+			b.append(((JSpinner)c).getValue());
+		}else{
+			b.append(' ').append(c.getAccessibleContext().getAccessibleRole());
+		}
+		return b.toString();
+	}
+	
+	@SuppressWarnings("serial")
+	public static void changeTabListener(JComponent component, final Container container){
+		/* remove the default tab traversal key from all the containers */
+		disableTraversalKey(component);
+		/* get the look and feel default keys for moving the focus on (usually = TAB) */
+		for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS))
+			component.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 */
+		component.getActionMap().put("tab", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
+				Component next = policy.getComponentAfter(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
+				next.requestFocusInWindow();
+				NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(next));
+			}
+		});
+		
+		for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS))
+			component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"back_tab");
+		
+		component.getActionMap().put("back_tab", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
+				Component previous = policy.getComponentBefore(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
+				previous.requestFocusInWindow();
+				NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(previous));
+			}
+		});
+	}
+	
+	private static void disableTraversalKey(Container container){
+		for(final Component c : container.getComponents()){
+			if(c instanceof Container){
+				c.setFocusTraversalKeysEnabled(false);
+				disableTraversalKey((Container)c);
+			}
+		}
+	}
+	
+	public static KeyListener getSpeechKeyListener(boolean editableComponent){
+		if(!editableComponent)
+			return new SpeechKeyListener(false);
+		return speechKeyListener;
+	}
+	
+	public static ItemListener getSpeechComboBoxItemListener(){
+		return comboBoxItemListener;
+	}
+	
+	public static ItemListener getCheckBoxSpeechItemListener(){
+		return checkBoxItemListener;
+	}
+	
+	public static FocusListener getFocusSpeechListener(){
+		return focusListener;
+	}
+	
+	public static Action getShutUpAction(){
+		return shutUpAction;
+	}
+	
+	/*
+	 * this class manages the speech feedback when moving around a text component
+	 * with the up, down, left and right arrows
+	 */
+	private static class SpeechKeyListener extends KeyAdapter{
+		boolean isTab;
+		boolean isBeginning;
+		boolean isFirstLine;
+		boolean isLastLine;
+		boolean editableComponent;
+		
+		SpeechKeyListener(boolean editablecomponent){
+			this.editableComponent = editablecomponent;
+		}
+		
+		@Override
+		public void keyTyped(KeyEvent evt){
+			/* this will manage digit or letter characters */
+			if(!isTab && !evt.isControlDown() && editableComponent){
+				if(Character.isLetterOrDigit(evt.getKeyChar())){
+					NarratorFactory.getInstance().speak(String.valueOf(evt.getKeyChar()));
+				}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"));
+						break;
+					case ' ' :
+						NarratorFactory.getInstance().speak(resources.getString("char.space"));
+						break;	
+					case '@' : 
+						NarratorFactory.getInstance().speak(resources.getString("char.at"));
+						break;
+					case '.' :
+						NarratorFactory.getInstance().speak(resources.getString("char.dot"));
+						break;
+					case ',' :
+						NarratorFactory.getInstance().speak(resources.getString("char.comma"));
+						break;
+					case ';' :
+						NarratorFactory.getInstance().speak(resources.getString("char.semi_colon"));
+						break;
+					case ':' :
+						NarratorFactory.getInstance().speak(resources.getString("char.colon"));
+						break;	
+					case '<' :
+						NarratorFactory.getInstance().speak(resources.getString("char.lower_than"));
+						break;
+					case '>' :
+						NarratorFactory.getInstance().speak(resources.getString("char.greater_than"));
+						break;
+					case '#' :
+						NarratorFactory.getInstance().speak(resources.getString("char.sharp"));
+						break;
+					case '~' : 
+						NarratorFactory.getInstance().speak(resources.getString("char.tilde"));
+						break;
+					case '+' : 
+						NarratorFactory.getInstance().speak(resources.getString("char.plus"));
+						break;
+					case '-' : 
+						NarratorFactory.getInstance().speak(resources.getString("char.dash"));
+						break;
+					case '_' : 
+						NarratorFactory.getInstance().speak(resources.getString("char.underscore"));
+						break;	
+					case '/' :
+						NarratorFactory.getInstance().speak(resources.getString("char.slash"));
+						break;
+					}
+				}
+		 	}
+			isTab = false;
+		}
+		
+		/* manages all the non digit or letter characters */
+		@Override
+		public void keyPressed(KeyEvent e){
+			int caretPos = ((JTextComponent)e.getSource()).getCaretPosition();
+			String text = ((JTextComponent)e.getSource()).getText();
+			
+			if (e.getKeyCode() == KeyEvent.VK_TAB){
+				isTab = true;
+			}
+			if(caretPos == 0)
+				isBeginning = true;
+			else
+				isBeginning = false;
+			
+			isFirstLine = true;
+			for(int i=0; i<caretPos;i++){
+				if(text.charAt(i) == '\n'){
+					isFirstLine = false;
+					break;
+				}
+			}
+			
+			if(text.indexOf('\n', caretPos) == -1)
+				isLastLine = true;
+			else
+				isLastLine = false;
+		}
+		
+		@Override
+		public void keyReleased(KeyEvent evt){
+			JTextComponent textComponent = (JTextComponent)evt.getSource();
+			String text;
+			int begin,end,caretPos;
+			
+			switch(evt.getKeyCode()){
+			case KeyEvent.VK_BACK_SPACE:
+				NarratorFactory.getInstance().speak(resources.getString("char.back_space"));
+				break;
+			case KeyEvent.VK_DELETE :
+				NarratorFactory.getInstance().speak(resources.getString("char.delete"));
+				break;
+			case KeyEvent.VK_LEFT :
+			case KeyEvent.VK_RIGHT :
+				try {
+					if(evt.getKeyCode() == KeyEvent.VK_LEFT){ //left
+						if(textComponent.getCaretPosition() == 0 && isBeginning){
+							SoundFactory.getInstance().play(SoundEvent.ERROR);
+							return;
+						}
+					}else{ // right
+						if(textComponent.getCaretPosition() == textComponent.getText().length()){
+							SoundFactory.getInstance().play(SoundEvent.ERROR);
+							return;
+						}
+					}
+					NarratorFactory.getInstance().speak(textComponent.getText(textComponent.getCaretPosition(),1));
+				} catch (BadLocationException e1) {
+					e1.printStackTrace();
+				}
+				break;
+			case KeyEvent.VK_UP :
+				/* when moving up and down, the line we land on is spoken out (the whole line). If the border
+				 * (top/bottom most line) is reached then the error sound is played. on a JTextField this
+				 * is the default behaviour with up and down keys as we only have one line
+				 */	
+				
+				if(isFirstLine){//we're on the first line and cannot go any upper
+					SoundFactory.getInstance().play(SoundEvent.ERROR);
+					return;
+				}
+				
+				text = textComponent.getText();
+				caretPos = textComponent.getCaretPosition();
+				
+				/* look for the beginning of the row the cursor is */
+				begin = 0;
+				for(int i=0; i<caretPos;i++){
+					if(text.charAt(i) == '\n')
+						begin = i+1;
+				}
+				
+				/* now the end */
+				end = text.indexOf('\n', caretPos);
+				
+				if(end == begin)//in case it's an empty line
+					end++;
+				NarratorFactory.getInstance().speak(text.substring(begin, end));
+				break;
+			case KeyEvent.VK_DOWN :
+				if(isLastLine){ //no new line we either have one line only or sit on the last one
+					SoundFactory.getInstance().play(SoundEvent.ERROR);
+					return;
+				}
+				
+				text = textComponent.getText();
+				caretPos = textComponent.getCaretPosition();
+				
+				begin = 0;
+				for(int i=0;i<caretPos;i++){
+					if(text.charAt(i) == '\n')
+						begin = i+1;
+				}
+				begin = Math.min(begin, text.length()-1);
+				
+				end = text.indexOf('\n', begin);
+				if(end == -1) // the line we're looking for is the last one 
+					end = text.length()-1;
+				
+				if(end == begin) // in case it's an empty line
+					end++;
+				NarratorFactory.getInstance().speak(text.substring(begin, end));
+				break;
+			}
+		}
+	}
+	
+	private static final ResourceBundle resources = ResourceBundle.getBundle(Narrator.class.getName());
+	private static final SpeechKeyListener speechKeyListener = new SpeechKeyListener(true);
+	private static final ItemListener comboBoxItemListener = new ItemListener(){
+		@Override
+		public void itemStateChanged(ItemEvent evt) {
+			if(evt.getStateChange() == ItemEvent.SELECTED)
+				NarratorFactory.getInstance().speak(evt.getItem().toString());
+		}
+	};
+	
+	private static final ItemListener checkBoxItemListener = new ItemListener(){
+		@Override
+		public void itemStateChanged(ItemEvent evt) {
+			NarratorFactory.getInstance().speak(getComponentSpeech(((JCheckBox)evt.getItemSelectable())));
+		}
+	};
+	
+	private static final FocusListener focusListener = new FocusAdapter(){
+		public void focusGained(FocusEvent evt){
+			NarratorFactory.getInstance().speak(getComponentSpeech(evt.getComponent()));
+		}
+	};
+	
+	@SuppressWarnings("serial")
+	private static final Action shutUpAction = new AbstractAction(){
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			NarratorFactory.getInstance().shutUp();
+		}
+	};
+}
Binary file java/src/uk/ac/qmul/eecs/ccmi/speech/WinNarrator.dll has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/CCmIUncaughtExceptionHandler.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,73 @@
+/*  
+ 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.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.logging.FileHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The UncaughtExceptionHandler for the CCmI Editor. It logs the occurred exception stack trace 
+ * on a file (errorN.log, where N is an integer number automatically assigned to avoid
+ * collision with other files of the same type) which is created in the same directory where
+ * the program is run. The exception stack trace will be in the format defined by the {@code XMLFormatter} class
+ * of the java.utli.logging package.
+ * 
+ * @see java.util.logging.XMLFormatter
+ */
+public class CCmIUncaughtExceptionHandler implements UncaughtExceptionHandler {
+	
+	@Override
+	public void uncaughtException(Thread thread, Throwable throwable) {
+		try{
+			Logger logger = Logger.getLogger("uncaught_exception");
+			logger.setLevel(Level.SEVERE);
+			FileHandler fileHandler = null;
+			try {
+				fileHandler = new FileHandler("error%u.log",true); 
+			} catch (IOException e) {
+				System.err.println(throwable.toString());
+				System.err.println();
+				System.err.println("Could not use error log file");
+				e.printStackTrace();
+				return;
+			}
+			fileHandler.setLevel(Level.SEVERE);
+			logger.addHandler(fileHandler);
+			StringBuilder builder = new StringBuilder(throwable.toString());
+			builder.append('\n');
+			final Writer result = new StringWriter();
+		    final PrintWriter printWriter = new PrintWriter(result);
+		    throwable.printStackTrace(printWriter);
+		    builder.append(result.toString());
+			logger.severe(builder.toString());
+			fileHandler.close();
+			throwable.printStackTrace();
+		}catch(Exception exception){
+			exception.printStackTrace();
+			System.exit(-1);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/CharEscaper.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,96 @@
+/*  
+ 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;
+
+/**
+ * A utility class providing static methods to escape one or more characters. 
+ *
+ */
+public class CharEscaper {
+	/**
+	 * Replaces the new line character with a '|' in the {@code String} passed as argument. 
+	 * The original {@code String} can be restored by passing the returned {@code String} to 
+	 * {@link #restoreNewline(String)}. Existing '|' characters will be escaped in order not to miss
+	 * them in the restore process.  
+	 * @param s the {@String} to remove new line characters from
+	 * @return a {@String} where all new line characters have been replaced by '|'
+	 */
+	public 	static String replaceNewline(String s){
+		String result = s.replace("|", "'|");
+		return result.replace('\n', '|');
+	}
+	
+	/**
+	 * Restores a {@code String} whose new line characters have been previously replaced by 
+	 * {@link #replaceNewline(String)}, to the original form.  
+	 * @param s
+	 * @return
+	 */
+	public static String restoreNewline(String s){
+		String result = s.replaceAll("([^'])\\|","$1\n");
+		return result.replace("'|", "|");
+	}
+	
+	/**
+	 * Escapes a set of character with another character.
+	 * @param s The {@code String} whose characters must be escaped. 
+	 * @param charsToEscape The set of characters to escape
+	 * @param escapeChar The escape character
+	 * @return a new {@code String} where characters in {@code charsToEscape} have been escaped 
+	 * by {@code escapeChar}.
+	 */
+	public static String escapeCharSequence(String s, CharSequence charsToEscape, char escapeChar ){
+		String result = s;
+		for(int i=0;i< charsToEscape.length();i++){
+			char c = charsToEscape.charAt(i);
+			if(c == escapeChar)
+				throw new IllegalArgumentException("escape character cannot be in chars to escape sequence");
+			for(int j=0;j<i;j++)
+				if(charsToEscape.charAt(j) == c)
+					throw new IllegalArgumentException("chars to escape sequence can only have unique characters");
+			result = result.replace(""+c, ""+escapeChar+c);
+		}
+		return result;
+	}
+	
+	/**
+	 * Removes an escape character preceding a set of character.
+	 * @param s The {@code String} containing the characters to un-escape
+	 * @param charsToUnescape The set of characters to the which must be un-escaped
+	 * @param escapeChar The escape character 
+	 * @return A {@code String} where the escape character preceding the characters 
+	 * in charsToUnescape have been removed.  
+	 */
+	public static String unescapeCharSequence(String s, CharSequence charsToUnescape, char escapeChar ){
+		String result = s;
+		for(int i=0;i< charsToUnescape.length();i++){
+			char c = charsToUnescape.charAt(i);
+			if(c == escapeChar)
+				throw new IllegalArgumentException("escape character cannot be in chars to escape sequence");
+			for(int j=0;j<i;j++)
+				if(charsToUnescape.charAt(j) == c)
+					throw new IllegalArgumentException("chars to escape sequence can only have unique characters");
+			result = result.replace(""+escapeChar+c,""+c);
+		}
+		return result;
+	}
+	
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/ExceptionHandler.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,24 @@
+/*  
+ 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;
+
+public interface ExceptionHandler {
+	void handleException(Exception e);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/GridBagUtilities.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,87 @@
+/*  
+ 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.awt.GridBagConstraints;
+
+/**
+ * 
+ * A Utility class providing static method to quickly arrange components, laid out by 
+ * a GridBagLayout, in the following way: one component per row and 
+ * either taking the whole column or just the right part of it, if preceded by a label.  
+ *
+ *
+ */
+public class GridBagUtilities {
+	public GridBagUtilities(){
+		 labelPad = DEFAULT_LABEL_PAD;
+		 row = 0;
+	}
+	
+	public GridBagConstraints label(int pad){
+		GridBagConstraints c ;
+		
+		c = new GridBagConstraints();
+		c.anchor = GridBagConstraints.WEST;
+		c.gridx = 0;
+		c.gridy = row;
+		c.insets = new java.awt.Insets(PAD,PAD,PAD,pad);
+		
+		return c;
+	}
+	
+	public GridBagConstraints label(){
+		return label(labelPad);
+	}
+	
+	public void setLabelPad(int labelPad){
+		this.labelPad = labelPad;
+	}
+	
+	public GridBagConstraints field(){
+		GridBagConstraints c;
+		
+		c = new GridBagConstraints();
+		c.anchor = GridBagConstraints.CENTER;
+		c.gridx = 1;
+		c.gridy = row++;
+		c.insets = new java.awt.Insets(PAD,PAD,PAD,PAD);
+		c.fill = GridBagConstraints.HORIZONTAL;
+		
+		return c;
+	}
+	
+	public GridBagConstraints all(){
+		GridBagConstraints c;
+		
+		c = new GridBagConstraints();
+		c.gridy = row++;
+		c.anchor = GridBagConstraints.CENTER;
+		c.gridwidth = GridBagConstraints.REMAINDER;
+		c.fill = GridBagConstraints.HORIZONTAL;
+		c.insets = new java.awt.Insets(PAD,PAD,PAD,PAD);
+		return c;
+	}
+	
+	private int labelPad;
+	private int row;
+	public static final int DEFAULT_LABEL_PAD = 50;
+	public static final int PAD = 2;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/InteractionLog.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,112 @@
+/*  
+ 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.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.FileHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * A logger class using the {@code java.util.logging} package to log all the user's 
+ * relevant actions. 
+ */
+public class InteractionLog {
+	public static void enable(String logFileDir) throws IOException{
+		logger.setLevel(Level.FINE);
+		logger.setUseParentHandlers(false);
+		if(fileHandler == null){
+			fileHandler = new FileHandler(logFileDir+System.getProperty("file.separator")+"interaction%u.log",true);
+			fileHandler.setFormatter(new CCmILogFormatter());
+			logger.addHandler(fileHandler);
+		}
+		
+		/* also print the log on the console */
+		java.util.logging.ConsoleHandler ch = new java.util.logging.ConsoleHandler(); 
+		ch.setLevel(Level.ALL);
+		ch.setFormatter(new CCmILogFormatter());
+		logger.addHandler(ch);
+	}
+	
+	public static void disable(){
+		logger.setLevel(Level.OFF);
+	}
+	
+	public static CCmILogFormatter newFormatter(){
+		return new CCmILogFormatter();
+	}
+	
+	public static void log(String source, String action, String args){
+		StringBuilder builder = new StringBuilder(source);
+		builder.append(SEPARATOR)
+			.append(action)
+			.append(SEPARATOR)
+			.append(args);
+
+		logger.fine(builder.toString());
+	}
+	
+	public static void log(String msg){
+		logger.config(msg);
+	}
+	
+	public static class CCmILogFormatter extends Formatter{
+
+		private CCmILogFormatter(){
+			super();
+		}
+		
+		@Override
+		public String format(LogRecord record) {
+			StringBuilder builder = new StringBuilder();
+			if(record.getLevel() == Level.CONFIG){
+				SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss");
+				builder.append("--- ")
+					.append(dateFormat.format(new Date(record.getMillis())))
+					.append(" - ")
+					.append(record.getMessage())
+					.append(" ---")
+					.append(NEW_LINE);
+			}else if(record.getLevel() == Level.FINE){
+				SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
+				builder.append(dateFormat.format(new Date(record.getMillis())))
+					.append(SEPARATOR)
+					.append(record.getMessage())
+					.append(NEW_LINE);
+			}
+			
+			return builder.toString();
+		}
+	}
+	
+	public static void dispose(){
+		if(fileHandler != null)
+			fileHandler.close();
+	}
+	
+	private static Logger logger = Logger.getLogger("interaction");
+	private static FileHandler fileHandler;
+	private static char SEPARATOR = ',';
+	private final static String NEW_LINE = System.getProperty("line.separator");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/NativeLibFileWriter.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,87 @@
+/*  
+ 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/OsDetector.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,42 @@
+/*  
+ 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;
+
+/**
+ * 
+ * Provides static methods to probe which Operating System we're running on
+ *
+ */
+public class OsDetector {
+	
+	public static boolean isWindows(){
+		return(OS.indexOf( "win" ) >= 0);
+	}
+	
+	public static boolean isMac(){
+		return(OS.indexOf( "mac" ) >= 0);
+	}
+	
+	public static boolean isUnix(){
+		return(OS.indexOf( "nix") >=0 || OS.indexOf( "nux") >=0);
+	}
+
+	static final String OS = System.getProperty("os.name").toLowerCase();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/Pair.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,69 @@
+/*  
+ 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;
+
+/**
+ * 
+ * A Pair of objects.
+ *
+ * @param <T1> The first item type 
+ * @param <T2> The second item type
+ */
+public class Pair<T1,T2> {
+	public Pair(T1 first, T2 second){
+		this.first = first;
+		this.second = second;
+	}
+	
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((first == null) ? 0 : first.hashCode());
+		result = prime * result + ((second == null) ? 0 : second.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		@SuppressWarnings("rawtypes")
+		Pair other = (Pair) obj;
+		if (first == null) {
+			if (other.first != null)
+				return false;
+		} else if (!first.equals(other.first))
+			return false;
+		if (second == null) {
+			if (other.second != null)
+				return false;
+		} else if (!second.equals(other.second))
+			return false;
+		return true;
+	}
+
+	public T1 first;
+	public T2 second;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/PreferencesService.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,91 @@
+/*  
+ 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/)
+
+ 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.util.prefs.Preferences;
+
+/**
+ * A service for storing and loading user preferences.
+ */
+public abstract class PreferencesService
+{
+   /**
+    * Gets an instance of the service, suitable for the package of the given class.
+    * @return an instance of the service
+    */
+   public static PreferencesService getInstance(){
+      if (service != null) return service;
+      try{
+         service = new DefaultPreferencesService();
+         return service;
+      }
+      catch (SecurityException exception){
+         // that happens when we run under Web Start         
+      }
+      
+      return new NullPreferencesService();
+   }
+   
+   /**
+    * Gets a previously stored string from the service.
+    * @param key the key of the string
+    * @param defval the value to return if no matching value was found
+    * @return the value stored with the given key, or defval if none was found 
+    */
+   public abstract String get(String key, String defval);
+   /**
+    * Saves a key/value pair for later retrieval.
+    * @param key the key of the string to be stored
+    * @param value the value to to be stored
+    */
+   public abstract void put(String key, String value);
+   
+   private static PreferencesService service;
+}
+
+/**
+ * The default preferences service that uses the java.util.prefs API. 
+ */
+class DefaultPreferencesService extends PreferencesService{   
+   /**
+    * Gets an instance of the service, suitable for the package of the given class.
+    * @param appClass the main application class (only the package name is used as the path to  
+    * app-specific preferences storage)
+    * @return an instance of the service
+    */
+   public DefaultPreferencesService()
+   {
+      prefs = Preferences.userNodeForPackage(this.getClass());   
+   }
+   
+   public String get(String key, String defval) { return prefs.get(key, defval); }
+   public void put(String key, String defval) { prefs.put(key, defval); }
+
+   private Preferences prefs;
+}
+
+/**
+ * The null preferences service that is returned when we are an applet. 
+ */
+class NullPreferencesService extends PreferencesService {   
+   public String get(String key, String defval) { return defval; }
+   public void put(String key, String defval) { }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/Validator.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,33 @@
+/*  
+ 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.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Validator {
+	
+	public static boolean validateIPAddr(String addr){
+		Matcher m = ip.matcher(addr);
+		return m.matches();
+	}
+	
+	private static Pattern ip = Pattern.compile("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/license.txt	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/ReadMe.txt	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,33 @@
+========================================================================
+    CONSOLE APPLICATION : WinNarrator Project Overview
+========================================================================
+
+AppWizard has created this WinNarrator application for you.
+
+This file contains a summary of what you will find in each of the files that
+make up your WinNarrator application.
+
+
+WinNarrator.vcproj
+    This is the main project file for VC++ projects generated using an Application Wizard.
+    It contains information about the version of Visual C++ that generated the file, and
+    information about the platforms, configurations, and project features selected with the
+    Application Wizard.
+
+WinNarrator.cpp
+    This is the main application source file.
+
+/////////////////////////////////////////////////////////////////////////////
+Other standard files:
+
+StdAfx.h, StdAfx.cpp
+    These files are used to build a precompiled header (PCH) file
+    named WinNarrator.pch and a precompiled types file named StdAfx.obj.
+
+/////////////////////////////////////////////////////////////////////////////
+Other notes:
+
+AppWizard uses "TODO:" comments to indicate parts of the source code you
+should add to or customize.
+
+/////////////////////////////////////////////////////////////////////////////
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/WinNarrator.cpp	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,11 @@
+// WinNarrator.cpp : Defines the entry point for the console application.
+//
+
+#include "stdafx.h"
+
+
+int _tmain(int argc, _TCHAR* argv[])
+{
+	return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/WinNarrator.vcproj	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="9.00"
+	Name="WinNarrator"
+	ProjectGUID="{21E0931A-4AE4-4665-801B-DE5D38DF9588}"
+	RootNamespace="WinNarrator"
+	Keyword="Win32Proj"
+	TargetFrameworkVersion="196613"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory="..\workspace\ccmi"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="2"
+			CharacterSet="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="&quot;C:\Program Files\Java\jdk1.6.0_25\include&quot;;&quot;C:\Program Files\Java\jdk1.6.0_25\include\win32&quot;"
+				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				UsePrecompiledHeader="2"
+				WarningLevel="3"
+				DebugInformationFormat="4"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				LinkIncremental="2"
+				GenerateDebugInformation="true"
+				SubSystem="1"
+				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|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="2"
+			CharacterSet="1"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				EnableIntrinsicFunctions="true"
+				AdditionalIncludeDirectories="&quot;C:\Program Files\Java\jdk1.6.0_25\include&quot;;&quot;C:\Program Files\Java\jdk1.6.0_25\include\win32&quot;"
+				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				UsePrecompiledHeader="2"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				LinkIncremental="1"
+				GenerateDebugInformation="true"
+				SubSystem="1"
+				OptimizeReferences="2"
+				EnableCOMDATFolding="2"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+			>
+			<File
+				RelativePath=".\stdafx.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath=".\uk_ac_qmul_eecs_ccmi_speech_NativeWinNarrator.cpp"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Header Files"
+			Filter="h;hpp;hxx;hm;inl;inc;xsd"
+			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+			>
+			<File
+				RelativePath=".\stdafx.h"
+				>
+			</File>
+			<File
+				RelativePath=".\targetver.h"
+				>
+			</File>
+			<File
+				RelativePath=".\uk_ac_qmul_eecs_ccmi_speech_NativeWinNarrator.h"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Resource Files"
+			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+			>
+		</Filter>
+		<File
+			RelativePath=".\ReadMe.txt"
+			>
+		</File>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/stdafx.cpp	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,28 @@
+/*  
+ 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/>.
+*/
+
+
+// stdafx.cpp : source file that includes just the standard includes
+// WinNarrator.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/stdafx.h	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,34 @@
+/*  
+ 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/>.
+*/
+
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#include <stdio.h>
+#include <tchar.h>
+
+
+
+// TODO: reference additional headers your program requires here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/targetver.h	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,32 @@
+/*  
+ 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/>.
+*/
+
+#pragma once
+
+// The following macros define the minimum required platform.  The minimum required platform
+// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run 
+// your application.  The macros work by enabling all features available on platform versions up to and 
+// including the version specified.
+
+// Modify the following defines if you have to target a platform prior to the ones specified below.
+// Refer to MSDN for the latest info on corresponding values for different platforms.
+#ifndef _WIN32_WINNT            // Specifies that the minimum required platform is Windows Vista.
+#define _WIN32_WINNT 0x0600     // Change this to the appropriate value to target other versions of Windows.
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/uk_ac_qmul_eecs_ccmi_speech_NativeWinNarrator.cpp	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,88 @@
+/*  
+ 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/>.
+*/
+
+#include "stdafx.h"
+#include <sapi.h>
+#include "uk_ac_qmul_eecs_ccmi_speech_NativeWinNarrator.h"
+#include <iostream>
+
+void __speak(JNIEnv *env, jstring text, DWORD dwFlags);
+
+ISpVoice * pVoice;
+
+JNIEXPORT jboolean JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1init
+(JNIEnv *env, jobject obj){
+	pVoice = NULL;
+    if (FAILED(::CoInitialize(NULL)))
+        return JNI_FALSE;
+
+    HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
+
+	if( SUCCEEDED( hr ) )
+		return JNI_TRUE;
+	else
+		return JNI_FALSE;
+}
+
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1dispose
+(JNIEnv *env, jobject obj){
+	pVoice->Release();
+	pVoice = NULL;
+	::CoUninitialize();
+}
+
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1speak
+(JNIEnv *env, jobject obj, jstring text){
+	__speak(env,text,SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_NOT_XML);
+}
+
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1speakWholeText
+(JNIEnv *env, jobject obj, jstring text){
+	__speak(env,text,SPF_DEFAULT | SPF_PURGEBEFORESPEAK | SPF_IS_NOT_XML);
+}
+
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1setRate
+(JNIEnv *env, jobject obj, jint rate){
+	pVoice->SetRate(rate-10);
+}
+
+void __speak(JNIEnv *env, jstring text, DWORD dwFlags){
+	int len = env->GetStringLength(text);
+	const jchar* jcharBuffer = env->GetStringChars(text,NULL);
+	if(jcharBuffer == NULL){
+		return;
+	}
+
+	WCHAR* unicodeBuffer = new  WCHAR[len+1];			
+	memcpy(unicodeBuffer, jcharBuffer, len*sizeof(jchar));
+	unicodeBuffer[len] = 0;
+	
+	pVoice->Speak(unicodeBuffer,
+		dwFlags,
+		NULL);
+
+	/* release allocated memory */
+	env->ReleaseStringChars(text,jcharBuffer);
+	delete [] unicodeBuffer;
+}
+
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1shutUp
+(JNIEnv *env, jobject obj){
+	pVoice->Speak(NULL,SPF_PURGEBEFORESPEAK,NULL);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/native/WinNarrator/uk_ac_qmul_eecs_ccmi_speech_NativeWinNarrator.h	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,79 @@
+/*  
+ 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/>.
+*/
+
+#include <jni.h>
+/* Header for class uk_ac_qmul_eecs_ccmi_speech_NativeNarrator */
+
+#ifndef _Included_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+#define _Included_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+ * Method:    _init
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1init
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+ * Method:    _dispose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1dispose
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+ * Method:    _speak
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1speak
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+ * Method:    _speakWholeText
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1speakWholeText
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+ * Method:    _setRate
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1setRate
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     uk_ac_qmul_eecs_ccmi_speech_NativeNarrator
+ * Method:    _shutUp
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_ccmi_speech_NativeNarrator__1shutUp
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif