diff java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java @ 0:9418ab7b7f3f

Initial import
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Fri, 16 Dec 2011 17:35:51 +0000
parents
children 4b2f975e35fa
line wrap: on
line diff
--- /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);
+		}		
+	}
+}