changeset 6:1c5af356bb99

added 64 bit native narrator allow hapitic native dll to load only on 32 bit JVM refactored DiagramEditorApp for better inheritance fixed Java 7 bug: NullPointerException when typing minor bug fixes added splashscreen
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Mon, 17 Dec 2012 18:39:40 +0000
parents d66dd5880081
children 075ae9eb2a40
files java/src/splash.jpeg 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/FileService.java java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java java/src/uk/ac/qmul/eecs/ccmi/gui/ResourceFactory.java java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java java/src/uk/ac/qmul/eecs/ccmi/haptics/FalconHaptics.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/OmniHaptics.java java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.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/speech/NativeNarrator.java java/src/uk/ac/qmul/eecs/ccmi/speech/WinNarrator64.dll java/src/uk/ac/qmul/eecs/ccmi/utils/OsDetector.java
diffstat 21 files changed, 344 insertions(+), 149 deletions(-) [+]
line wrap: on
line diff
Binary file java/src/splash.jpeg has changed
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java	Mon Dec 17 18:39:40 2012 +0000
@@ -114,7 +114,7 @@
  *  tree representations of diagrams.
  */
 @SuppressWarnings("serial")
-public class EditorFrame extends JFrame {
+public class EditorFrame extends JFrame { 
 	/**
 	 * Creates a new {@code EditorFrame}
 	 * 
@@ -122,9 +122,11 @@
 	 * @param templateFiles an array of template files. New diagrams can be created from template files by clonation 
 	 * @param backupDirPath the path of a folder where all the currently open diagrams will be saved if 
 	 * the haptic device crashes  
-	 * @param templateEditors the template editors for this instance of the program 
+	 * @param templateEditors the template editors for this instance of the program
+	 * @param additionalTemplate additional diagram templates. An entry will be created in {@code File->New Diagram}
+	 * for each template  
 	 */
-	public EditorFrame(Haptics haptics, File[] templateFiles, String backupDirPath, TemplateEditor[] templateEditors){  
+	public EditorFrame(Haptics haptics, File[] templateFiles, String backupDirPath, TemplateEditor[] templateEditors, Diagram[] additionalTemplate){  
 		this.backupDirPath = backupDirPath; 
 		/* load resources */ 
 		resources = ResourceBundle.getBundle(this.getClass().getName());      
@@ -209,16 +211,20 @@
 		/* 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();
+		initMenu(templateEditors);
 		/* read template files. this call must be placed after menu creation as it adds menu items to file->new-> */
 		boolean someTemplateFilesNotRead = readTemplateFiles(templateFiles);
+		
+		for(Diagram additionalDiagram : additionalTemplate){
+			addDiagramType(additionalDiagram);
+		}
+		
 		/* become visible */
 		pack();
 		setVisible(true);
@@ -303,10 +309,10 @@
 		};
 	}
 
-	private void initMenu(){
+	private void initMenu(TemplateEditor[] templateEditors){
 		ResourceFactory factory = new ResourceFactory(resources);
 
-		JMenuBar menuBar = factory.createMenuBar();
+		JMenuBar menuBar = SpeechMenuFactory.getMenuBar();
 		setJMenuBar(menuBar);
 
 		/* --- FILE MENU --- */
@@ -330,6 +336,9 @@
 
 		fileSaveAsItem = factory.createMenuItem("file.save_as", this, "saveFileAs");
 		fileMenu.add(fileSaveAsItem);
+		
+		fileSaveCopyItem = factory.createMenuItem("file.save_copy", this, "saveCopy");
+		fileMenu.add(fileSaveCopyItem);
 
 		fileCloseItem = factory.createMenuItem("file.close",this,"closeFile");
 		fileMenu.add(fileCloseItem);
@@ -503,78 +512,79 @@
 		}
 
 		/* --- TEMPLATE --- */
-		JMenu templateMenu = factory.createMenu("template");
-		menuBar.add(templateMenu);
+		if(templateEditors.length > 0){
+			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);
+			for(final TemplateEditor templateEditor : templateEditors){
+				JMenuItem newDiagramItem = SpeechMenuFactory.getMenuItem(templateEditor.getLabelForNew());
+				newDiagramItem.addActionListener(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,
-									MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
-									SpeechOptionPane.INFORMATION_MESSAGE);
-							SoundFactory.getInstance().play(SoundEvent.OK,null);
+									EditorFrame.this, 
+									resources.getString("dialog.error.save_template"));
+							return;
 						}
-					})
-			);
+						addDiagramType(diagram);
+						SpeechOptionPane.showMessageDialog(
+								EditorFrame.this,
+								MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
+								SpeechOptionPane.INFORMATION_MESSAGE);
+						SoundFactory.getInstance().play(SoundEvent.OK,null);
+					}
+				});
+				templateMenu.add(newDiagramItem);
 
-			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(
+				JMenuItem editDiagramItem = SpeechMenuFactory.getMenuItem(templateEditor.getLabelForEdit());
+				editDiagramItem.addActionListener(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.input.edit_diagram_template"), 
-									diagrams, 
-									diagrams[0]);
-
-							if(selectedDiagram == null){
-								SoundFactory.getInstance().play(SoundEvent.CANCEL);
-								return;
-							}
-
-							Diagram diagram = templateEditor.edit(EditorFrame.this, existingTemplateNames,selectedDiagram);
-							if(diagram == null)
-								return;
-							try{
-								saveDiagramTemplate(diagram);
-							}catch(IOException ioe){
-								SpeechOptionPane.showMessageDialog(
-										EditorFrame.this, 
-										resources.getString("dialog.error.save_template"));
-								return;
-							}
-							addDiagramType(diagram);
-							SpeechOptionPane.showMessageDialog(
-									EditorFrame.this,
-									MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
-									SpeechOptionPane.INFORMATION_MESSAGE);
-							SoundFactory.getInstance().play(SoundEvent.OK,null);
+									resources.getString("dialog.error.save_template"));
+							return;
 						}
+						addDiagramType(diagram);
+						SpeechOptionPane.showMessageDialog(
+								EditorFrame.this,
+								MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
+								SpeechOptionPane.INFORMATION_MESSAGE);
+						SoundFactory.getInstance().play(SoundEvent.OK,null);
 					}
-			));
+				}
+				);
+				templateMenu.add(editDiagramItem);
+			}
 		}
 
 		/* --- COLLABORATION ---- */
@@ -934,14 +944,14 @@
 		DiagramPanel diagramPanel = getActiveTab();
 		if (diagramPanel == null) // no tabs open  
 			return false;
-		String fileName = diagramPanel.getFilePath();
-		if (fileName == null) { 
+		String filePath = diagramPanel.getFilePath();
+		if (filePath == null) { 
 			return saveFileAs(); 
 		}
 
 		OutputStream out = null;
 		try{
-			File file = new File(fileName);
+			File file = new File(filePath);
 			out = new BufferedOutputStream(new FileOutputStream(file));
 			Diagram d = diagramPanel.getDiagram();
 			PersistenceManager.encodeDiagramInstance(d, out);
@@ -960,6 +970,41 @@
 	}
 
 	/**
+	 * Prompts the user with a dialog for saving a diagram on disk. The current diagram is not affected 
+	 * by this call and it keeps to be displayed in the editor. 
+	 *    
+	 * @return {@code true} if the file is successfully saved, or {@code false} otherwise.
+	 */
+	public boolean saveCopy(){
+		DiagramPanel diagramPanel = getActiveTab();
+		if (diagramPanel == null) // no tabs open  
+			return false;
+		OutputStream out = null;
+		FileService.Save save;
+		try {
+			save = fileService.save(
+					PreferencesService.getInstance().get("dir.diagrams", "."),
+					diagramPanel.getFilePath(), //default file to save to 
+					extensionFilter, 
+					null, 
+					defaultExtension,
+					null);
+			out = save.getOutputStream();
+			if (out == null)   /* user didn't select any file for saving */
+				return false;
+			PersistenceManager.encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
+			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();}
+		}
+	}
+	
+	/**
      * Saves the current diagram as a new file. The user is prompter with a {@code FileChooser}
      * to chose a file to save the diagram to.
      * 
@@ -969,6 +1014,7 @@
 		DiagramPanel diagramPanel = getActiveTab();
 		if (diagramPanel == null) // no tabs open  
 			return false;
+		String oldName = diagramPanel.getDiagram().getName();
 		OutputStream out = null;
 		try {
 			String[] currentTabs = new String[editorTabbedPane.getTabCount()];
@@ -987,10 +1033,22 @@
 				return false;
 
 			PersistenceManager.encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
-			/* update the diagram panel, after the saving */
+			/* update the diagram and the diageam panel, after the saving */
+			diagramPanel.getDiagram().setName(save.getName());
 			diagramPanel.setFilePath(save.getPath());
 			diagramPanel.setModified(false);
 			speakFocusedComponent(resources.getString("dialog.file_saved"));
+			/* update the haptics, remove first the diagram that is going o be renamed * 
+			 * and then re-add the diagram under the new name (avoids haptics          *  
+			 * IllegalArgumentException when switching tab)                            */ 
+			haptics.removeDiagram(oldName, null);
+			haptics.addNewDiagram(diagramPanel.getDiagram().getName());
+			for(Node n : diagramPanel.getDiagram().getCollectionModel().getNodes())
+				haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),null);
+			for(Edge e : diagramPanel.getDiagram().getCollectionModel().getEdges()){
+				Edge.PointRepresentation pr = e.getPointRepresentation();
+				haptics.addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),null);
+			}
 			return true;
 		}catch(IOException ioe){
 			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
@@ -1658,8 +1716,13 @@
 				server.share(diagram);
 				ProtocolFactory.newInstance().send(localSocket, new Command(Command.Name.LOCAL,diagram.getName(),DiagramEventSource.NONE));
 				dPanel.setDiagram(NetDiagram.wrapLocalHost(diagram,localSocket,server.getLocalhostQueue(diagram.getName()),netLocalDiagramExceptionHandler)); 
+				/* share a diagram once is enought :) */
 				shareDiagramMenuItem.setEnabled(false);
+				/* no close, there might be clients connected. unshare first. */
 				fileCloseItem.setEnabled(false);
+				/* only enabled for local diagram, otherwise changing the name * 
+				 * of the diagram would messes up the network protocol         */
+				fileSaveAsItem.setEnabled(false);
 				dPanel.setAwarenessPanelEnabled(true);
 			} catch (IOException e) {
 				iLog("error sharing diagram",diagram.getName()+" "+e.getLocalizedMessage());
@@ -2118,8 +2181,8 @@
 		/* 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(){
+		JMenuItem newTypeItem = SpeechMenuFactory.getMenuItem(diagram.getName());
+		newTypeItem.addActionListener(new ActionListener(){
 			@Override
 			public void actionPerformed(ActionEvent event){
 				Diagram clone = (Diagram)diagram.clone();
@@ -2141,7 +2204,8 @@
 				addTab(null, clone);
 				iLog("new diagram created of type: "+diagram.getName());
 			}
-		}));
+		});
+		newMenu.add(newTypeItem);
 	}
 
 	/**
@@ -2190,7 +2254,7 @@
 			haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),null);
 		for(Edge e : diagram.getCollectionModel().getEdges()){
 			Edge.PointRepresentation pr = e.getPointRepresentation();
-			HapticsFactory.getInstance().addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),null);
+			haptics.addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),null);
 		}
 		/* install the listener that handling the haptics device and the one handling the audio feedback */
 		diagram.getCollectionModel().addCollectionListener(hapticTrigger);
@@ -2210,6 +2274,7 @@
 	private void diagramPanelEnabledMenuUpdate(DiagramPanel dPanel){
 		fileSaveItem.setEnabled(false);
 		fileSaveAsItem.setEnabled(false);
+		fileSaveCopyItem.setEnabled(false);
 		fileCloseItem.setEnabled(false);
 		shareDiagramMenuItem.setEnabled(false);
 		graphExportItem.setEnabled(false);
@@ -2219,13 +2284,17 @@
 			return;
 
 		fileSaveItem.setEnabled(true);
-		fileSaveAsItem.setEnabled(true);
+		fileSaveCopyItem.setEnabled(true);
 		graphExportItem.setEnabled(true);
 		if(dPanel.getDiagram() instanceof NetDiagram){
 			if(dPanel.isAwarenessPanelVisible()) 
 				hideAwarenessPanelMenuItem.setEnabled(true);
 			else
 				showAwarenessPanelMenuItem.setEnabled(true);
+		}else{
+			/* only enabled for local diagram, otherwise changing the name * 
+			 * of the diagram would messes up the network protocol         */
+			fileSaveAsItem.setEnabled(true);
 		}
 
 		boolean isSharedDiagram = dPanel.getDiagram() instanceof NetDiagram; 
@@ -2326,7 +2395,6 @@
 	private PreferencesService preferences;
 	private HapticTrigger hapticTrigger;
 	private DiagramElement hapticHighlightDiagramElement;
-	private TemplateEditor[] templateEditors;
 	private ArrayList<String> existingTemplateNames;
 	private ArrayList<Diagram> existingTemplates;
 	private AwarenessPanelEnablingListener awarenessPanelListener;
@@ -2337,6 +2405,7 @@
 	private JMenuItem fileSaveItem;
 	private JMenuItem graphExportItem;
 	private JMenuItem fileSaveAsItem;
+	private JMenuItem fileSaveCopyItem;
 	private JMenuItem fileCloseItem;
 	private JMenuItem insertMenuItem;
 	private JMenuItem deleteMenuItem;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties	Mon Dec 17 18:39:40 2012 +0000
@@ -31,7 +31,7 @@
 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.license.title=License 
 
 dialog.ok_button=Ok
 dialog.cancel_button=Cancel
@@ -71,11 +71,11 @@
 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.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=Set label
+dialog.input.edge_operation.label=Set Label
 dialog.input.edge_operation.arrow_head=Set Arrow Head
 dialog.input.edge_operation.title= Edge End Operation
 dialog.input.edge_operation.select=What would you like to do ?
@@ -107,6 +107,7 @@
 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.lock_failure.no_edge_creation="One or more nodes are candidates for creation by another user"
 
 dialog.property_editor.title=Property Editor
 dialog.property_editor.error.property_null=Properties cannot be null
@@ -200,6 +201,7 @@
 file.save.text=Save
 file.save.mnemonic=S
 file.save.accelerator=ctrl S
+file.save_copy.text=Save a copy...
 file.save_as.text=Save as...
 #file.save_as.mnemonic=A
 file.close.text=Close
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java	Mon Dec 17 18:39:40 2012 +0000
@@ -153,6 +153,7 @@
 		 * @param removeExtension the extension to be removed from the chosen file name. Use {@code null} for removing no extension
 		 * @param addExtension the extension to be added to the chosen file name.
 		 * @param currentTabs an array of already open files names. If the selected file matches any of these, then an
+		 *      Exception is thrown. If {@code null} is passed, then this parameter will be ignored and no check will be done. 
 		 * @return an {@code FileService.Save} to handle the file selected by the user 
 		 * @throws IOException if the file chosen by the uses doesn't exist or has a file name that's already 
 		 * in {@code currentTabs}.  
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java	Mon Dec 17 18:39:40 2012 +0000
@@ -674,14 +674,30 @@
 		@Override
 		public void edgeCreated(Edge e) {
 			ArrayList<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(selectedElements.size());
+			
 			for(DiagramElement element : selectedElements){
-				if(element instanceof Node)
+				if(element instanceof Node){
+					if(!modelUpdater.getLock(element, 
+							Lock.MUST_EXIST,
+							new DiagramEventActionSource(DiagramEventSource.GRPH, 
+									Command.Name.SELECT_NODE_FOR_EDGE_CREATION,
+									element.getId(),
+									element.getName()))){
+						/* unlock the nodes locked so far  */
+						yieldLocks(nodesToConnect);
+						/* notify user */
+						JOptionPane.showMessageDialog(GraphPanel.this, 
+								ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.lock_failure.no_edge_creation"));
+						return;
+					}
 					nodesToConnect.add((Node)element);
+				}
 			}
 			try {
 				e.connect(nodesToConnect);
-				/* perform the command, no lock is needed for inserting */
 				modelUpdater.insertInCollection(e,DiagramEventSource.GRPH);
+				/* release the must-exist lock on nodes now that the edge is created */
+				yieldLocks(nodesToConnect);
 			} catch (ConnectNodesException cnEx) {
 				JOptionPane.showMessageDialog(GraphPanel.this,
 						cnEx.getLocalizedMessage(),
@@ -690,6 +706,20 @@
 				iLog("insert edge error",cnEx.getMessage());
 			}
 		}
+		
+		/* release all locks */
+		private void yieldLocks(ArrayList<DiagramNode> nodesToConnect){
+			for(DiagramNode node : nodesToConnect){
+				modelUpdater.yieldLock(node, 
+						Lock.MUST_EXIST, 
+						new DiagramEventActionSource(
+								DiagramEventSource.GRPH,
+								Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION,
+								node.getId(),
+								node.getName())
+				);
+			}
+		}
 	}
 
 	private List<Edge> edges;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java	Mon Dec 17 18:39:40 2012 +0000
@@ -249,6 +249,7 @@
 	 * 
 	 * @param doc An XMl document
 	 * @param nodeTag the XML {@code PersistenceManager.NODE } tag with data for this node
+	 * @throws IOException if something goes wrong when reading the document. E.g. when the file is corrupted 
 	 * 
 	 * @see uk.ac.qmul.eecs.ccmi.gui.persistence
 	 */
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/ResourceFactory.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/ResourceFactory.java	Mon Dec 17 18:39:40 2012 +0000
@@ -26,22 +26,39 @@
 import java.util.ResourceBundle;
 
 import javax.swing.JMenu;
-import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.KeyStroke;
 
 /**
- * A factory class for swing components creation support. Components that are created via this 
- * class are sonyfied via the Narrator
+ * A factory class for swing components creation support. 
+ * 
+ * Components that are created via this class are, in turn created by {@code SpeechMenuFactory} methods.
+ * This class handles the labelling and other menu properties (such as accelerators) 
+ * using the {@code ResourceBundle} passed as argument to the constructor.
  */
 class ResourceFactory{
    public ResourceFactory(ResourceBundle bundle){
       this.bundle = bundle;
    }
 
-   
-   public JMenuBar createMenuBar(){
-	   return SpeechMenuFactory.getMenuBar();
+   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 JMenuItem createMenuItem(String prefix, 
@@ -58,12 +75,6 @@
       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");
@@ -71,7 +82,7 @@
       return configure(menuItem, prefix, listener);
    }
 
-   public JMenuItem configure(JMenuItem menuItem, 
+   private JMenuItem configure(JMenuItem menuItem, 
       String prefix, ActionListener listener){      
       menuItem.addActionListener(listener);
       try{
@@ -97,26 +108,5 @@
       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;
-   }
-   
    private ResourceBundle bundle;
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java	Mon Dec 17 18:39:40 2012 +0000
@@ -29,11 +29,13 @@
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
+import javax.swing.ComponentInputMap;
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JComponent;
 import javax.swing.JMenu;
 import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
 import javax.swing.MenuElement;
 import javax.swing.MenuSelectionManager;
 
@@ -51,15 +53,7 @@
 	/* implements the singleton pattern and keeps a static reference to the menuBar used by 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(resources.getString("menufactory.leaving"));
-					}
-				}
-			};
+			menuBar = new SpeechMenuBar();
 		}
 		return menuBar;
 	}
@@ -105,7 +99,26 @@
 		}
 	};
 	
-	private static final String ACCELERATOR = "accelerator";
+	private static class SpeechMenuBar extends JMenuBar{
+		SpeechMenuBar() {
+			setInputMap(SpeechMenuBar.WHEN_IN_FOCUSED_WINDOW, new ComponentInputMap(this){
+				@Override
+				public Object get(KeyStroke keyStroke){
+					if(keyStroke == null) 
+						return null;
+					return super.get(keyStroke);
+				}
+			});
+		}
+		
+		@Override
+		public void	processKeyEvent(KeyEvent e, MenuElement[] path, MenuSelectionManager manager) {
+			super.processKeyEvent(e,path,manager);
+			if(e.getKeyCode() == KeyEvent.VK_ESCAPE){
+				NarratorFactory.getInstance().speak(resources.getString("menufactory.leaving"));
+			}
+		}
+	}
 	
 	/*
 	 * this class implements a menu item which speaks out its label when 
@@ -150,6 +163,8 @@
 		@Override
 		public void setEnabled(boolean b){
 			super.setEnabled(b);
+			if(getAccelerator() == null)
+				return;
 			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) 
@@ -210,4 +225,5 @@
 	
 	private static JMenuBar menuBar;
 	private static ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+	private static final String ACCELERATOR = "accelerator";
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java	Mon Dec 17 18:39:40 2012 +0000
@@ -170,7 +170,10 @@
 	 * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html
 	 * 
 	 * @param diagram the diagram to encode
-	 * @param newName the new name of the diagram to encode. This will also be the name of the file 
+	 * @param newName the new name of the diagram to encode. This will also be the name of the file but {@code diagram}
+	 * will still keep the old name, that is the value returned by {@code getName()} won't be changed to {@code newName}. 
+	 * If a {@code null} value is passed than the value returned by {@code diagram.getName()} will be used.
+	 * 
 	 * @param out where the diagram will be encoded
 	 * @throws IOException if there are any I/O problems with the file 
 	 */
@@ -277,8 +280,6 @@
 		} catch (TransformerException te) {
 			throw new IOException(resources.getString("dialog.error.problem.save"),te);
 		}
-		if(newName != null)
-			diagram.setName(newName);
 	}
 	
 	/**
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/FalconHaptics.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/FalconHaptics.java	Mon Dec 17 18:39:40 2012 +0000
@@ -37,6 +37,10 @@
 
 class FalconHaptics extends Thread implements Haptics {
 	static Haptics createInstance(HapticListenerThread listener) {
+		if(OsDetector.has64BitJVM()){// no 64 native library supported yet
+			return null;
+		}
+		
 		if(OsDetector.isWindows()){
 			/* create a directory for the dll distributed with HAPI library */
 			String libDir = PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir"));
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java	Mon Dec 17 18:39:40 2012 +0000
@@ -33,6 +33,13 @@
 
 	public void switchDiagram(String diagramName);
 
+	/**
+	 * Removes a diagram from the collection of haptic diagrams.  
+	 * 
+	 * @param diagramNameToRemove the unique name of the diagram to remove
+	 * @param diagramNameOfNext the name of the next diagram to render hapticly. If 
+	 *        {@code null}, no diagram will be rendered.  
+	 */
 	public void removeDiagram(String diagramNameToRemove, String diagramNameOfNext);
 
 	public void addNode(double x, double y, int nodeHashCode, String diagramName);
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java	Mon Dec 17 18:39:40 2012 +0000
@@ -45,19 +45,27 @@
 		 * a conflict between function names.                                                     */
 		String defaultDevice = PreferencesService.getInstance().get("haptic_device", TABLET_ID);
 		
+		/* temporary set the preference to default: if the device crashes during init * 
+		 * the user will still be able to restart the diagram editor                  */
+		PreferencesService.getInstance().put("haptic_device", TABLET_ID);
+		
 		if(PHANTOM_ID.equals(defaultDevice)){
 			/* OmniHaptics first */
 			hapticsInstance = OmniHaptics.createInstance(listener);
-			if(hapticsInstance != null)//OmniHaptics instance successfully created: return
+			if(hapticsInstance != null){//OmniHaptics instance successfully created: return
+				PreferencesService.getInstance().put("haptic_device", defaultDevice);
 				return;
+			}
 		}else if(FALCON_ID.equals(defaultDevice)){ 
 			/* Falcon first  */
 			hapticsInstance = FalconHaptics.createInstance(listener);
 			if(hapticsInstance != null){ //FalconHaptics instance successfully created: return
+				PreferencesService.getInstance().put("haptic_device", defaultDevice);
 				return;
 			}
 		}
 		
+		PreferencesService.getInstance().put("haptic_device", defaultDevice);
 		/* no devices available, stop the listener and go for the default */
 		if(listener.isAlive()){
 			listener.interrupt();
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java	Mon Dec 17 18:39:40 2012 +0000
@@ -45,6 +45,10 @@
 		if(listener == null)
 			throw new IllegalArgumentException("listener cannot be null");
 		
+		if(OsDetector.has64BitJVM()){
+			return null;// no 64 native library supported yet
+		}
+		
 		if(OsDetector.isWindows()){
 			/* try to load .dll. First copy it in the home/ccmi_editor_data/lib directory  */
 			URL url = OmniHaptics.class.getResource("OmniHaptics.dll");
--- a/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java	Mon Dec 17 18:39:40 2012 +0000
@@ -30,6 +30,7 @@
 import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
 
+import uk.ac.qmul.eecs.ccmi.gui.Diagram;
 import uk.ac.qmul.eecs.ccmi.gui.EditorFrame;
 import uk.ac.qmul.eecs.ccmi.gui.HapticKindle;
 import uk.ac.qmul.eecs.ccmi.gui.SpeechOptionPane;
@@ -184,21 +185,64 @@
 	 */
 	@Override
 	public void run() {
-		editorFrame = new EditorFrame(haptics,templateFiles,backupDirPath,getTemplateEditors());
+		editorFrame = new EditorFrame(haptics,getTemplateFiles(),backupDirPath,getTemplateEditors(),getDiagrams());
 	}
 	
-	public TemplateEditor[] getTemplateEditors(){
+	/**
+	 * Provides template editors to create own templates using the diagram editor.
+	 * 
+	 * Subclasses who don't want any template editor to appear in the diagram 
+	 * can just return an empty array. Returning {@code null} will throw an exception.
+	 * 
+	 * @return an array of template editors 
+	 */
+	protected TemplateEditor[] getTemplateEditors(){
 		TemplateEditor[] templateEditors = new TemplateEditor[1];
 		templateEditors[0] = new SimpleTemplateEditor();
 		return templateEditors;
 	}
+	
+	/**
+	 * Returns the template files detected in the ccmi_editor_data/templates directory.
+	 * 
+	 * Returning {@code null} will throw an exception.
+	 * 
+	 * @return an array of (xml) Files containing a template 
+	 */
+	protected File[] getTemplateFiles(){
+		return templateFiles;
+	}
+	
+	/**
+	 * Returns an empty list. This method can be overwritten by subclasses to 
+	 * provide their own custom diagrams. Such diagrams will appear in the menu.
+	 * 
+	 * Returning {@code null} will throw an exception.
+	 * 
+	 * @return an array of diagram templates. The array is empty in this implementation. 
+	 */
+	protected Diagram[] getDiagrams(){
+		return new Diagram[0];
+	}
 
 	/**
 	 * The main function 
 	 * @param args this software accepts no args from the command line
 	 */
-	public static void main(String[] args) {	
-		DiagramEditorApp application = new DiagramEditorApp();
+	public static void main(String[] args){
+		DiagramEditorApp application = new DiagramEditorApp();	
+		mainImplementation(application);
+	}
+	
+	
+	/**
+	 * Implementation of the main body. It can be used to run the program 
+	 * using a subclass of {@code DiagramEditorApp}, providing it's own
+	 * diagram templates 
+	 *  
+	 * @param application the diagram editor application to execute 
+	 */
+	public final static void mainImplementation(DiagramEditorApp application) {	
 		try{
 			application.init();
 		} catch (IOException e) {
@@ -244,7 +288,7 @@
 		return editorFrame;
 	}
 	
-	static EditorFrame editorFrame;
+	private static EditorFrame editorFrame;
 	Haptics haptics;
 	File[] templateFiles;
 	TemplateEditor[] templateCreators;
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java	Mon Dec 17 18:39:40 2012 +0000
@@ -95,7 +95,9 @@
 						""+AwarenessMessage.USERNAMES_SEPARATOR+unsc.userName
 						);
 				for(UserNameSocketChannel userNameSocketChannel : entry.getValue()){
-					protocol.send(userNameSocketChannel.channel, awMsg);
+					/* don't send aw msg to the local channel */
+					if(!userNameSocketChannel.channel.equals(localChannel))
+						protocol.send(userNameSocketChannel.channel, awMsg);
 				}
 				break;
 			}
@@ -188,7 +190,7 @@
 			/* it's NULL for NOTES lock and SELECT_NODE_FOR_EDGE_CREATION must not clean the text panel, because its record is temporary */ 
 			if(released && source != DiagramEventActionSource.NULL && source.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION){ 
 				
-				if(source.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION){
+				if(source.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION && broadcastFilter.accept(source)){
 					/* unselect node for edge creation is treated differently because it doesn't * 
 					 * clear the text panel but adds another record, which is temporary          */
 					handleAwarenessMessage(new AwarenessMessage(
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java	Mon Dec 17 18:39:40 2012 +0000
@@ -451,6 +451,13 @@
 	private static final Rectangle2D.Double minBounds = new Rectangle2D.Double(0,0,DEFAULT_WIDTH,DEFAULT_HEIGHT);
 	private static final int PROP_NODE_DIST = 50;
 	
+	/**
+	 * When properties are configure to appear outside, the values are represented as small nodes 
+	 * connected (with a straight line) to the node they belong to. This class represents such 
+	 * small nodes. The possible shapes are: triangle, rectangle, square, circle, ellipse and no shape in 
+	 * which case only the string with the property value and the line connecting it to the node is shown. 
+	 *
+	 */
 	protected static class PropertyNode{
 		public PropertyNode(ShapeType aShape){
 			/* add a little padding in the shape holding the label */
--- a/java/src/uk/ac/qmul/eecs/ccmi/sound/BeadsSound.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/BeadsSound.java	Mon Dec 17 18:39:40 2012 +0000
@@ -20,7 +20,7 @@
 package uk.ac.qmul.eecs.ccmi.sound;
 
 import java.io.InputStream;
-import java.util.HashMap;
+import java.util.EnumMap;
 import java.util.Map;
 
 import net.beadsproject.beads.core.AudioContext;
@@ -34,14 +34,14 @@
 
 /**
  * The Sound interface implementation using the Beads library.
- * For more info abou the library see http://www.beadsproject.net/.
+ * For more info about 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>();
+		playerListeners = new EnumMap<SoundEvent,PlayerListener>(SoundEvent.class);
+		loopPlayers = new EnumMap<SoundEvent,UGen>(SoundEvent.class);
 		
 		/* pre load all the sample to avoid future overhead */
 		for(SoundEvent key : AudioResourcesService.eventTypes()){
--- a/java/src/uk/ac/qmul/eecs/ccmi/sound/PlayerListener.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/sound/PlayerListener.java	Mon Dec 17 18:39:40 2012 +0000
@@ -22,9 +22,12 @@
 /**
  * {@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. 
+ * will be triggered just after the sound is played. 
  *
  */
 public interface PlayerListener {
+	/**
+	 * Called when the sound is played
+	 */
 	void playEnded();
 }
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java	Mon Dec 17 18:39:40 2012 +0000
@@ -37,12 +37,13 @@
 	static {
 		nativeLibraryNotFound = true;
 		if(OsDetector.isWindows()){
-			URL url = NativeNarrator.class.getResource("WinNarrator.dll");
+			String res = OsDetector.has64BitJVM() ? "WinNarrator64.dll" : "WinNarrator.dll" ;
+			URL url = NativeNarrator.class.getResource(res);
 			if(url != null){
 				ResourceFileWriter fileWriter = new ResourceFileWriter(url);
 				fileWriter.writeOnDisk(
 					PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),	
-					"CCmIWinNarrator.dll");
+					OsDetector.has64BitJVM() ? "CCmIWinNarrator64.dll" : "CCmIWinNarrator.dll");
 				String path = fileWriter.getFilePath();
 				if(path != null)
 					try{
Binary file java/src/uk/ac/qmul/eecs/ccmi/speech/WinNarrator64.dll has changed
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/OsDetector.java	Tue Jul 10 22:39:37 2012 +0100
+++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/OsDetector.java	Mon Dec 17 18:39:40 2012 +0000
@@ -38,5 +38,10 @@
 		return(OS.indexOf( "nix") >=0 || OS.indexOf( "nux") >=0);
 	}
 
+	public static boolean has64BitJVM(){
+		String dataModel = System.getProperty("sun.arch.data.model");
+		return dataModel.equals("64");
+	}
+	
 	static final String OS = System.getProperty("os.name").toLowerCase();
 }