view src/uk/ac/qmul/eecs/ccmi/activities/TreeNavigation.java @ 1:66b3a838feca logging tip

Added logging of user interaction
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Tue, 12 Feb 2013 15:31:48 +0000
parents e0ee6ac3a45f
children
line wrap: on
line source
/*  
 CCmI Diagram Editor for Android 
  
 Copyright (C) 2012  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.activities;

import java.util.ArrayList;
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 uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService;
import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService.SoundEvent;
import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleCheckbox;
import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleDialogBuilder;
import uk.ac.qmul.eecs.ccmi.utilities.ILogger;
import uk.ac.qmul.eecs.ccmi.utilities.Stack;
import uk.ac.qmul.eecs.ccmi.xmlparser.Diagram;
import uk.ac.qmul.eecs.ccmi.xmlparser.DiagramUpdater;
import uk.ac.qmul.eecs.ccmi.xmlparser.Edge;
import uk.ac.qmul.eecs.ccmi.xmlparser.EdgeNode;
import uk.ac.qmul.eecs.ccmi.xmlparser.EdgeType;
import uk.ac.qmul.eecs.ccmi.xmlparser.HierarchyItem;
import uk.ac.qmul.eecs.ccmi.xmlparser.LocalDiagramUpdater;
import uk.ac.qmul.eecs.ccmi.xmlparser.Modifier;
import uk.ac.qmul.eecs.ccmi.xmlparser.Node;
import uk.ac.qmul.eecs.ccmi.xmlparser.NodeProperty;
import uk.ac.qmul.eecs.ccmi.xmlparser.NodePropertyType;
import uk.ac.qmul.eecs.ccmi.xmlparser.NodeType;
import uk.ac.qmul.eecs.ccmi.xmlparser.PropertyValue;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.Spinner;

/**
 * Provides a navigation of the model (the diagram) based on the tree hierarchy of the 
 * <a href="http://ccmi.eecs.qmul.ac.uk">CCmI Diagram Editor</a> 
 *
 */
class TreeNavigation {
	
	private static final String RENAME_DIALOG_TAG = "Rename ";
	private static final String EDIT_NODE_DIALOG_TAG = "Edit Node Dialog";
	private static final String EDIT_EDGE_DIALOG_TAG = "Edit Edge Dialog";
	private static final String EDIT_NODE_REF_DIALOG_TAG = "Edit Node Reference Dialog";
	private static final String EDIT_LABEL_DIALOG_TAG = "Edit Label";
	private static final String EDIT_ARROWHEAD_DIALOG_TAG = "Edit Arrow Head Dialog";
	private static final String CONFIRMATION_DIALOG_TAG = "Are you sure you want to delete";
	private static final String ADD_PROPERTY_DIALOG_TAG = "Add Property Dialog";
	private static final String EDIT_PROPERTY_DIALOG_TAG = "Edit Property Dialog";
	private static final String EDIT_MODIFIERS_DIALOG_TAG = "Edit Modifiers Dialog";
	
	private Diagram diagram;
	private DiagramUpdater diagramUpdater;
	private Stack<HierarchyItem> path;
	
	private List<Node> selectedNodes = new ArrayList<Node>(4);
	/* cached list is filled up when next() and previous() and used to undo the navigation   
	 * if the list created is empty. Once created, then it's returned by getCurrentList() */
	private String[] cachedList;
	private Stack<String> cachedHeaderTexts;
	
	/* controller for edit actions with access to the current status of the navigation */
	private Controller controller;
	private Updateable updateable;
	
	/* the level currently displayed. Level item's toString() is on the header *  
	 * text and its children are displayed in the currentList                  */
	private final static int PATH_MAX_LEN = 5;
	public final static int DIAGRAM_LEVEL = 0;
	public final static int TYPE_LEVEL = 1;
	public final static int ITEM_LEVEL = 2;
	public final static int REFERENCE_AND_PROPERTY_TYPE_LEVEL = 3;
	public final static int PROPERTY_AND_EDGE_REFERENCE_LEVEL = 4; // neved used
	
	public TreeNavigation(Diagram diagram, AccessibleDialogBuilder dialogBuilder, Updateable updateable){
		this.diagram = diagram;
		this.updateable = updateable;
		diagramUpdater = new LocalDiagramUpdater(diagram);
		path = new Stack<HierarchyItem>(PATH_MAX_LEN);
		cachedHeaderTexts = new Stack<String>(PATH_MAX_LEN);
		/* tree path starts with top node */
		path.push(diagram);
		cachedHeaderTexts.push(diagram.toString());
		cachedList = buildCurrentChildList();
		controller = new Controller(dialogBuilder);
	}
	
	public String[] getCurrentChildList(){
		return cachedList;
	}
	
	public String getCurrentPath(){
		StringBuilder builder = new StringBuilder();
		for(HierarchyItem item : path){
			builder.append('/').append(item);
		}
		return builder.toString();
	}
	
	/* builds the String list of items to display when getCurrentListIsCalled */
	private String[] buildCurrentChildList(){
		String[] currentList = null;
		switch(path.level()){
		case DIAGRAM_LEVEL :{
			List<NodeType> nodeTypes = diagram.getPrototypes().getNodeTypes();
			List<EdgeType> edgeTypes = diagram.getPrototypes().getEdgeTypes();
			int numNodeTypes = nodeTypes.size();
			int numEdgeTypes = edgeTypes.size();
			currentList = new String[numNodeTypes+numEdgeTypes];
	
			/* build the string for each node type */
			StringBuilder builder = new StringBuilder();
			for(int i=0; i<numNodeTypes;i++){
				builder.setLength(0);
				builder.append(nodeTypes.get(i).getType())
					.append(' ')
					.append('(')
					.append(count(nodeTypes.get(i)))
					.append(')');
				currentList[i] = builder.toString();
			}
			/* build the string for each edge type */
			for(int j=0; j<numEdgeTypes; j++){
				builder.setLength(0);
				builder.append(edgeTypes.get(j).getType())
					.append(' ')
					.append('(')
					.append(count(edgeTypes.get(j)))
					.append(')');
				currentList[numNodeTypes+j] = builder.toString();
			}
			
			break;
		}
		case TYPE_LEVEL : {
			/* list all objects of a certain type */
			LinkedList<String> items = new LinkedList<String>();
			if(path.current() instanceof NodeType){
				for(Node cNode : diagram.getComponents().getNodes()){
					if(path.current().toString().equals(cNode.getType()))
						if(selectedNodes.contains(cNode))
							items.add("<"+cNode+">"); // <selected node>
						else
							items.add(cNode.getName()); 
				}
			}else{
				for(Edge cEdge : diagram.getComponents().getEdges()){
					if(path.current().toString().equals(cEdge.getType()))
						items.add(cEdge.getName());
				}
			}
			currentList = items.toArray(new String[items.size()]);
			break;
		}
		case ITEM_LEVEL : {
			Map<String,Integer> edgeTypes = new LinkedHashMap<String,Integer>();
			if(path.current() instanceof Node){
				/* node displays the list of edge types connected to      * 
				 * itself + the list of property types defined for itself */
				Node node = (Node)path.current();
				/* add the edge types of the edges the node is attached to */
				for(Edge cEdge : diagram.getComponents().getEdges()){
					for(EdgeNode attachedNode : cEdge.getAttachedNodes()){
						if(attachedNode.getId() == node.getId()){
							if(!edgeTypes.containsKey(cEdge.getType())){
								edgeTypes.put(cEdge.getType(),1);
							}else{
								edgeTypes.put(cEdge.getType(),edgeTypes.get(cEdge.getType())+1);
							}
							break;	
						}
					}
				}
				
				/* build the array to return : first part is the edge types. second part property types /
				/* now the property types this node can have */
				currentList = new String[edgeTypes.size()+node.getProperties().size()];
				int i = 0;
				StringBuilder builder = new StringBuilder();
				for(Map.Entry<String, Integer> entry : edgeTypes.entrySet()){
					builder.setLength(0);
					currentList[i++] = builder.append(entry.getKey())
											.append(' ')
											.append('(')
											.append(entry.getValue())
											.append(')').toString();
				}
				
				for(int j=i;j<currentList.length;j++){
					builder.setLength(0);
					currentList[j] = builder.append(node.getProperties().get(j-i).getType())
											.append(' ')
											.append('(')
											.append(node.getProperties().get(j-i).getValues().size())
											.append(')').toString();
				}
				
			}else{ // Edge
				/* edge displays the list of nodes attached to self */
				Edge cEdge = (Edge)path.current();
				currentList = new String[cEdge.getAttachedNodes().size()];
				int i = 0;
				for(EdgeNode attachedNode : cEdge.getAttachedNodes()){
					for(Node node : diagram.getComponents().getNodes()){
						if(node.getId() == attachedNode.getId()){
							currentList[i++] = (attachedNode.getHead()+" "+node.getName()+" "+attachedNode.getLabel()).trim();
						}
					}
				}
			}
			break;
		}
		case REFERENCE_AND_PROPERTY_TYPE_LEVEL : {
			/* display the values of the selected property types */
			if(path.current() instanceof NodeProperty){  // property type is displayed on the header
				NodeProperty property = (NodeProperty)path.current();
				currentList = new String[property.getValues().size()];
				NodeType nodeType = (NodeType)path.get(TYPE_LEVEL);
				
				/* for each property value build the string "mod1 mod2 mod3...value" * 
				 * where modn is the modifier type and value is the property value   */
				StringBuilder builder = new StringBuilder();
				for(int i=0; i<currentList.length; i++){
					builder.setLength(0);
					List<Integer> modifierIndexes = property.getValues().get(i).getModifiersIndexes();
					for(NodePropertyType propertyType : nodeType.getPropretyTypes()){
						if(propertyType.getType().equals(property.getType())){
							ArrayList<Modifier> modifiers = propertyType.getModifiers();
							for(Integer j : modifierIndexes){
								builder.append(modifiers.get(j).getType()).append(' ');
							}
						}
					}
					currentList[i] = builder.append(property.getValues().get(i).getValue()).toString();
				}
				
//				NodeProperty propertyType = (NodeProperty)path.current();
//				Node node = (Node)path.get(ITEM_LEVEL);
//				for(NodeProperty property : node.getProperties()){
//					if(property.getType().equals(propertyType.getType())){
//						currentList = new String[property.getValues().size()];
//						for(int i=0; i<currentList.length; i++){
//							currentList[i] = property.getValues().get(i).getValue();
//						}
//						break;
//					}
//				}
			}else{ // instance of EdgeType 
				EdgeType edgeType = (EdgeType)path.current();
				LinkedList<String> currentListItems = new LinkedList<String>();
				int nodeID = ((Node)path.get(ITEM_LEVEL)).getId();
				StringBuilder builder = new StringBuilder();
				for(Edge edge : diagram.getComponents().getEdges()){
					if(edge.getType().equals(edgeType.getType())){
						for(EdgeNode attachedNode : edge.getAttachedNodes()){
							if(attachedNode.getId() == nodeID){
								currentListItems.add(makeEdgeReferenceString(edge,nodeID,builder));
							}
						}
					}
				}
				currentList = currentListItems.toArray(new String[currentListItems.size()]);
			}
			break;
		}
		default : new IllegalStateException("Wrong path level: "+path.level());
		}
		return currentList;
	}

	/**
	 * Returns the label of the parent of the list currently visualized 
	 * 
	 * @return the label of the parent 
	 */
	public String getCurrentItemName(){
		String text = cachedHeaderTexts.current();
		if(text != null)
			return text;
		else	
			return path.current().toString();
	}
	
	/**
	 * Returns the level of the navigation at the moment the method is called.
	 * 
	 * @return an {@code int} representing the current level. Possible values are 
	 * listed as {@code static} variables of this class. 
	 */
	public int getCurrentLevel(){
		return path.level();
	}
	
	/**
	 * Makes a child of the current {@code HierarchyItem} the current one. 
	 * 
	 * @param child the index of the child becoming the current 
	 * @return {@code true} if the current position has changed after the call
	 **/
	public boolean goNext(int child){
		if(child < 0)
			throw new IllegalArgumentException("Wrong child index: "+child);
		
		HierarchyItem hierarchyItem = findNext(child);
		if(hierarchyItem == null)
			return false;
		
		path.push(hierarchyItem);
		/* saves the selected list item to display as a title on the next call to getCurrentHeaderText() */
		cachedHeaderTexts.push(cachedList[child]);
		/* make the new list to return for next calls to getCurrentList() */
		cachedList = buildCurrentChildList();
		/* the selected hierarchy node has no children. go back and return false */
		if(cachedList.length == 0){
			goPrevious();
			return false;
		}
		updateable.update();
		return true;
	}
	
	private HierarchyItem findNext(int child){
		switch(path.level()){
		case DIAGRAM_LEVEL :{
			List<NodeType> nodeTypes = diagram.getPrototypes().getNodeTypes();
			List<EdgeType> edgeTypes = diagram.getPrototypes().getEdgeTypes();
			int numNodeTypes = nodeTypes.size();
			int numEdgeTypes = edgeTypes.size();
			if(child >= numNodeTypes+numEdgeTypes)
				throw new IllegalArgumentException("Wrong child index: "+child);
			
			if(child < numNodeTypes){
				return nodeTypes.get(child);
			}else{
				return edgeTypes.get(child-numNodeTypes);
			}
		}
		case TYPE_LEVEL : {
			int numTypeItems = 0;// the number of nodes or edges found of the current type 
			if(path.current() instanceof NodeType){
				/* find the child-th node of the current type */
				for(Node node : diagram.getComponents().getNodes()){
					if(path.current().toString().equals(node.getType())){
							if(numTypeItems == child){
								return node;
							}else{
								numTypeItems++;
							}
					}	
				}
			}else{ // EdgeType
				for(Edge edge : diagram.getComponents().getEdges()){
					/* find the child-th edge of the current type */ 
					if(path.current().toString().equals(edge.getType())){
							if(numTypeItems == child){
								return edge;
							}else{
								numTypeItems++;
							}
					}	
				}
			}
			break;
		}
		case ITEM_LEVEL : {
			/* we can go further only for nodes */
			if(path.current() instanceof Node){
				Node node = (Node)path.current();				
				/* check if user clicked on an edge, this node is attached to */
				
				Set<String> cNodeEdgeTypes = new LinkedHashSet<String>();
				/* for each edge component */
				for(Edge edge : diagram.getComponents().getEdges()){
					/* for each attached node to this edge */
					for(EdgeNode attachedNode : edge.getAttachedNodes()){
						/* if it's this node we're looking for (cNode) */
						if(attachedNode.getId() == node.getId()){
							cNodeEdgeTypes.add(edge.getType());
						}
					}
				}
				
				if(child < cNodeEdgeTypes.size()){
					String selectedType = cNodeEdgeTypes.toArray(new String[cNodeEdgeTypes.size()])[child];
					for(EdgeType edge : diagram.getPrototypes().getEdgeTypes()){
						if(edge.getType().equals(selectedType)){
							return edge;
						}
					}
				}else{// user selected a NodePropertyType put a NodeProperty in the path  
					return node.getProperties().get(child-cNodeEdgeTypes.size());
				}
				break;
			}
		}
		default : return null; // don't go further than REFERENCE_AND_PROPERTY_LEVEL
		}
		return null;
	}
	
	/**
	 * Makes the father of the current {@code HierarchyItem} the current one. 
	 * @return @code true} if the current position has changed after the call
	 */
	public boolean goPrevious(){
		if(path.level() == DIAGRAM_LEVEL) // level = DIAGRAM_LEVEL 
			return false;
		path.pop();
		cachedList = buildCurrentChildList();
		cachedHeaderTexts.pop();
		return true;
	}
	
	/**
	 * Returns a controller, that can be used to 
	 * change the model (the diagram) in a way coherent to the tree navigation 
	 * 
	 * @return a controller connected to this navigation
	 */
	public Controller getController(){
		return controller;
	}
	
	/* Counts the number of nodes or edges of a given type */
	private int count(HierarchyItem hItem){
		int count = 0;
		if(hItem instanceof NodeType){
			NodeType node = (NodeType)hItem;
			for(Node cNode : diagram.getComponents().getNodes())
				if(cNode.getType().equals(node.getType()))
					count++;
		}else if(hItem instanceof EdgeType){
			EdgeType edge = (EdgeType)hItem;
			for(Edge cEdge : diagram.getComponents().getEdges())
				if(cEdge.getType().equals(edge.getType()))
					count++;
		}
		return count;
	}
	
	/* Creates a string for the node reference of edge related to the node N equal to ID nodeID. 
	 * The string looks like "to N1, N2 and N3 vie E", where N1, N2 and N3 are the nodes connected
	 * to N via E ( they can be more than one in case of a multiple ended edge ).  
	 */
	private String makeEdgeReferenceString(Edge edge, int nodeID, StringBuilder builder) {
		builder.setLength(0);
		builder.append("to ");
		/* attach the name of the nodes interleaved by " and " */
		for(int i=0; i<edge.getAttachedNodes().size(); i++){
		    EdgeNode attachedNode = edge.getAttachedNodes().get(i);
			if(attachedNode.getId() != nodeID){
				for(Node cNode : diagram.getComponents().getNodes()){
					if(cNode.getId() == attachedNode.getId()){
						if(i == edge.getAttachedNodes().size()-1){
							builder.append(cNode.getName());
						}else if(i == edge.getAttachedNodes().size()-2){
							builder.append(cNode.getName()).append(" and ");							
						}else{
							builder.append(cNode.getName()).append(", ");
						}
						break;
					}
				}
			}
		}
		builder.append(", via ");
		builder.append(edge.getName());
		return builder.toString();
	}

	/* A controller that changes the model (the data structures in the xml parser package) using the informations  *
	 * given by the TreeNavigation. When commands are issued by the user (through long click) the controller pops  *
	 * up a dialog according to what the user chose and which layer of the tree they are currently visualizing     *     
	 * if the user issues the command (doesn't cancel the dialog) the model is updated and the updater             * 
	 * is called to refresh and visualize the result                                                               */
	class Controller implements AccessibleDialogBuilder.ButtonClickListener {
		AccessibleDialogBuilder dialogBuilder;
		HierarchyItem clickedItem;
		
		Controller(AccessibleDialogBuilder dialogBuilder){
			this.dialogBuilder = dialogBuilder;
		};
		
		/*
		 * Responds to the long click of the user. It acts according to the list item clicked
		 * by the user (and therefore according to the hierarchy level currently displayed).
		 * When a dialog needs to be shown, it used dialogBuilder.displayDialog() and register itself 
		 * as buttoClickListener to handle the click of the user in the same class.   
		 */
		public boolean performEditAction(AdapterView<?> parent, View view,
				int position, long id, Updateable u) {
			switch(getCurrentLevel()){
			case TreeNavigation.DIAGRAM_LEVEL : {
				List<NodeType> nodeTypes = diagram.getPrototypes().getNodeTypes();
				if(position < nodeTypes.size()){ // user clicked on a node
					clickedItem = nodeTypes.get(position);
					/* build the properties for node constructor using node property types */
					NodeType nodeType = nodeTypes.get(position);
					ArrayList<NodeProperty> properties = new ArrayList<NodeProperty>(nodeType.getPropretyTypes().size());
					for(NodePropertyType pType : nodeType.getPropretyTypes()){
						properties.add(new NodeProperty(pType.getType()));
					}
					/* construct the node and add it to the diagram */
					Node node = new Node(((NodeType)clickedItem).getType(),properties);
					diagramUpdater.addNode(node);
					dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_OK);
					dialogBuilder.getAccessibilityService().speak("Node "+ node +" created");
					ILogger.log("Node "+ node +" created");
				}else{ // user clicked on edge
					List<EdgeType> edgeTypes = diagram.getPrototypes().getEdgeTypes();
					clickedItem = edgeTypes.get(position - nodeTypes.size());
					/* respect min and max attached nodes */
					if(selectedNodes.size() < ((EdgeType)clickedItem).getMinAttachedNodes()){
						dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_ERROR);
						dialogBuilder.getAccessibilityService().speak("you must select at least "+
								((EdgeType)clickedItem).getMinAttachedNodes()+" nodes");
						ILogger.logError("selected nodes < "+((EdgeType)clickedItem).getMinAttachedNodes());
						return true;
					}
					if(selectedNodes.size() > ((EdgeType)clickedItem).getMaxAttachedNodes()){
						dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_ERROR);
						dialogBuilder.getAccessibilityService().speak("you must select at most "+
								((EdgeType)clickedItem).getMaxAttachedNodes()+" nodes");
						ILogger.logError("selected nodes > "+((EdgeType)clickedItem).getMaxAttachedNodes());
						return true;
					}
					
					/* create the edge and add the edge nodes */
					Edge edge = new Edge(((EdgeType)clickedItem).getType());
					edge.getAttachedNodes().clear();
					/* create all the node references (EdgeNode) for this edge */
					for(Node n : diagram.getComponents().getNodes()){
						if(selectedNodes.contains(n))
							edge.getAttachedNodes().add(new EdgeNode(n.getId()));
					}
					diagramUpdater.addEdge(edge);
					dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_OK);
					StringBuilder builder = new StringBuilder();
					builder.append(edge).append(" created between ");
					for(int i=0; i<selectedNodes.size(); i++){
						Node sn = selectedNodes.get(i);						
						builder.append(sn.getName());
						if(i == selectedNodes.size() - 2 ){
							builder.append(", and ");
						}else if (i != selectedNodes.size() - 1 ){
							builder.append(", ");
						}
					}
					selectedNodes.clear(); // when an edge is added the selected node are cleared
					dialogBuilder.getAccessibilityService().speak(builder.toString());
					ILogger.log(builder.toString());
				}
				/* update the view */
				cachedList = buildCurrentChildList();
				updateable.update();
				return true;
			}
			case TreeNavigation.TYPE_LEVEL :{
				clickedItem = findNext(position);
				/* path.current() is what's displayed on the header  */
				if(path.current() instanceof NodeType){ // Node
					if(selectedNodes.contains(clickedItem)){
						dialogBuilder.displayDialog(R.layout.alert_dialog_edit_node_unselect, EDIT_NODE_DIALOG_TAG, this);
					}else{
						dialogBuilder.displayDialog(R.layout.alert_dialog_edit_node, EDIT_NODE_DIALOG_TAG, this);
					}
				}else{ // Edge
					dialogBuilder.displayDialog(R.layout.alert_dialog_edit_edge, EDIT_EDGE_DIALOG_TAG, this);
				}
				return true;
			}
			case TreeNavigation.ITEM_LEVEL :{
				if(path.current() instanceof Node){ // Node
					clickedItem = findNext(position);
					/* edge types inside node cannot be edited directly */
					if(clickedItem instanceof EdgeType)
						return false;
					/* if it ain't an EdgeType then t's a PropertyType */
					/* property type items can be used to add new properties */
					dialogBuilder.displayDialog(R.layout.alert_dialog_add_property, ADD_PROPERTY_DIALOG_TAG, this);
				}else{ // Edge
					clickedItem = ((Edge)path.current()).getAttachedNodes().get(position);
					dialogBuilder.displayDialog(R.layout.alert_dialog_edit_node_reference, EDIT_NODE_REF_DIALOG_TAG, this);
				}
				return true;
			}
			case TreeNavigation.REFERENCE_AND_PROPERTY_TYPE_LEVEL :{
				if(path.current() instanceof NodeProperty){
					NodeProperty nodeProperty = (NodeProperty)path.current();
					clickedItem = nodeProperty.getValues().get(position);
					dialogBuilder.displayDialog(R.layout.alert_dialog_edit_property, EDIT_PROPERTY_DIALOG_TAG, this);
					return true;
				}
				return false;
			}
			case TreeNavigation.PROPERTY_AND_EDGE_REFERENCE_LEVEL :{ 
				return false;// never happens	
			}
			}
			return false;
		}

		/* this is the callback triggered when the user clicks on any botton of the dialog shown. v is the button 
		 * The method first checks for the dialog tag to understand which dialog it's handling, then it checks 
		 * for the button tag to understand which button the user pressed. The first check though is on "CANCEL"
		 * button as its tag it's the same for all the dialog */
		@Override
		public void onClick(View v, DialogFragment dialogFragment, String dialogTag) {
			Object buttonTag = v.getTag();
			ILogger.logButton(buttonTag.toString());
			AccessibilityService accessibility = dialogBuilder.getAccessibilityService();
			
			if("CANCEL".equals(buttonTag)){
				accessibility.playSound(SoundEvent.V_CANCEL);
				accessibility.speak("Cancel");
				dialogFragment.dismiss();
				return;
			}
			
			if(EDIT_NODE_DIALOG_TAG.equals(dialogTag) || EDIT_EDGE_DIALOG_TAG.equals(dialogTag)){
				if("SELECT".equals(buttonTag)){
					selectedNodes.add((Node)clickedItem);
					accessibility.playSound(SoundEvent.V_OK);
					accessibility.speak(clickedItem + " selected");
					ILogger.log(clickedItem + " selected");
				}else if("UNSELECT".equals(buttonTag)){
					selectedNodes.remove(clickedItem);
					accessibility.playSound(SoundEvent.V_OK);
					accessibility.speak(clickedItem + " unselected");
					ILogger.log(clickedItem + " unselected");
				}else if("RENAME".equals(buttonTag)){
					dialogFragment.dismiss();
					dialogBuilder.displayDialog(R.layout.alert_dialog_rename, RENAME_DIALOG_TAG+clickedItem, this);
				}else if("DELETE".equals(buttonTag)){
					dialogFragment.dismiss();
					dialogBuilder.displayDialog(R.layout.alert_dialog_confirmation, 
							CONFIRMATION_DIALOG_TAG+
								(EDIT_NODE_DIALOG_TAG.equals(dialogTag) ? " Node " : " Edge ") +
								clickedItem, 
							this);
				}
			}else if(dialogTag.startsWith(RENAME_DIALOG_TAG)){
				/* if it reached this point it'a "RENAME" button as "CANCEL" *
				 * have been matched against at the beginning of the method  */
				EditText editText = (EditText)dialogFragment.getDialog().findViewById(R.id.text_edit);
				String text = editText.getText().toString().trim();
				if(text.length() == 0){
					accessibility.playSound(SoundEvent.V_ERROR);
					accessibility.speak("Text cannot be empty");
					ILogger.logError("text empty");
					return;
				}
				String oldName = clickedItem.toString();
				diagramUpdater.rename(clickedItem,text);
				accessibility.playSound(SoundEvent.V_OK);
				accessibility.speak(oldName+" renamed to "+clickedItem);
				ILogger.log(oldName+" renamed to "+clickedItem);
			}else if(dialogTag.startsWith(CONFIRMATION_DIALOG_TAG)){
				/* if it reaches this point it's a "YES" as a "NO" button has "CANCEL" as its tag */
				/* and the match against "CANCEL" match is performed first of all */
				diagramUpdater.delete(clickedItem);
				/* if it's a selected node, remove it from selected */
				selectedNodes.remove(clickedItem);
				accessibility.playSound(SoundEvent.V_OK);
				accessibility.speak(clickedItem+" Deleted");
				ILogger.log(clickedItem+" Deleted");
				/* update the headers which show the number of children the current item contains */
				if(path.current() instanceof NodeType || path.current() instanceof EdgeType){
					cachedHeaderTexts.pop();
					cachedHeaderTexts.push(path.current()+" ("+count(path.current())+')');
				}else if(path.current() instanceof NodeProperty){
					cachedHeaderTexts.pop();
					cachedHeaderTexts.push(path.current()+" ("+((NodeProperty)path.current()).getValues().size()+')');
				}
			}else if(ADD_PROPERTY_DIALOG_TAG.equals(dialogTag)){
				/* if it reaches this point it's a "CREATE" as "CANCEL" would  *
				 * have been matched against at the beginning of the method    */
				EditText editText = (EditText)dialogFragment.getDialog().findViewById(R.id.text_edit);
				String text = editText.getText().toString().trim();
				if(text.length() == 0){
					accessibility.playSound(SoundEvent.V_ERROR);
					accessibility.speak("Text cannot be empty");
					ILogger.logError("text empty");
					return;
				}
				diagramUpdater.addProperty((Node)path.get(ITEM_LEVEL),((NodeProperty)clickedItem),text);
				accessibility.playSound(SoundEvent.V_OK);
				accessibility.speak("Property "+text+" added");
				ILogger.log("Property "+text+" added");
			}else if(EDIT_NODE_REF_DIALOG_TAG.equals(dialogTag)){
				if("EDIT_LABEL".equals(buttonTag)){
					dialogFragment.dismiss();
					dialogBuilder.displayDialog(R.layout.alert_dialog_rename, EDIT_LABEL_DIALOG_TAG, this);
				}else{  // EDIT_ARROWHEAD
					dialogFragment.dismiss();
					Edge edge = (Edge)path.current(); // get the edge this node reference belongs to 
					String[] heads = null;
					/* build a string array with all the available head labels for this edge's type */
					for(EdgeType edgeType : diagram.getPrototypes().getEdgeTypes()){
						if(edgeType.getType().equals(edge.getType())){
							heads = new String[edgeType.getHeads().size()];
							for(int i=0; i<heads.length;i++)
								heads[i] = edgeType.getHeads().get(i).getHeadLabel();
							break;
						}
					}
					if(heads == null || heads.length == 0){
						accessibility.playSound(SoundEvent.V_ERROR);
						accessibility.speak("There are no arrow heads defined for "+edge);
						ILogger.logError("There are no arrow heads defined for "+edge);
						return;
					}
					dialogBuilder.displaySelectionDialog(EDIT_ARROWHEAD_DIALOG_TAG, heads, this);
				}
			}else if(EDIT_LABEL_DIALOG_TAG.equals(dialogTag)){
				EditText editText = (EditText)dialogFragment.getDialog().findViewById(R.id.text_edit);
				String text = editText.getText().toString().trim();
				if(text.length() == 0){
					accessibility.playSound(SoundEvent.V_ERROR);
					accessibility.speak("Text cannot be empty");
					ILogger.logError("Text empty");
					return;
				}
				diagramUpdater.setLabel((EdgeNode)clickedItem,text);
				accessibility.playSound(SoundEvent.V_OK);
				accessibility.speak("Label set to "+clickedItem);// EdgeNode.toString = EdgeNode.getLabel
				ILogger.log("Label set to "+clickedItem);
			}else if(EDIT_ARROWHEAD_DIALOG_TAG.equals(dialogTag)){
				Spinner spinner = (Spinner)dialogFragment.getDialog().findViewById(R.id.selectionSpinner);
				diagramUpdater.setArrowHead((EdgeNode)clickedItem, spinner.getSelectedItem().toString());
				accessibility.playSound(SoundEvent.V_OK);
				accessibility.speak("Arrow head set to "+spinner.getSelectedItem().toString());
				ILogger.log("Arrow head set to "+spinner.getSelectedItem().toString());
			}else if(EDIT_PROPERTY_DIALOG_TAG.equals(dialogTag)){
				if("RENAME".equals(buttonTag)){
					dialogFragment.dismiss();
					dialogBuilder.displayDialog(R.layout.alert_dialog_rename, RENAME_DIALOG_TAG, this);
				}else if("DELETE".equals(buttonTag)){
					dialogFragment.dismiss();
					dialogBuilder.displayDialog(R.layout.alert_dialog_confirmation, 
							CONFIRMATION_DIALOG_TAG+ " property "+clickedItem, 
							this);
				}else if("MODIFIERS".equals(buttonTag)){
					dialogFragment.dismiss();
					/* get the modifiers from the NodePropertyType of the node where this property is */
					NodeProperty property = (NodeProperty)path.current();
					String[] modifiers = null;
					boolean[] checks = null;
					for( NodePropertyType propertyType : ((NodeType)path.get(TYPE_LEVEL)).getPropretyTypes()){
						if(propertyType.getType().equals(property.getType())){
							modifiers = new String[propertyType.getModifiers().size()];
							checks = new boolean[modifiers.length];
							for(int i=0; i< modifiers.length; i++){
								modifiers[i] = propertyType.getModifiers().get(i).getType();
								if(((PropertyValue)clickedItem).getModifiersIndexes().contains(i))
									checks[i] = true;
							}
						}
					}
					if(modifiers == null  || modifiers.length == 0){
						accessibility.playSound(SoundEvent.V_ERROR);
						accessibility.speak("There are no modifiers defined for "+property);
						return;
					}
					dialogBuilder.displayCheckDialog(EDIT_MODIFIERS_DIALOG_TAG, modifiers, checks, this);
				}
			}else if(EDIT_MODIFIERS_DIALOG_TAG.equals(dialogTag)){
				/* pressed button is OK as CANCEL would have been cought at the beginning */
				AccessibleCheckbox checkbox = (AccessibleCheckbox)dialogFragment.getDialog().findViewById(R.id.checkBox); 
				boolean[] checks = checkbox.getChecks();
				List<Integer> modifiers = new ArrayList<Integer>(checks.length);
				StringBuilder modifiersString = new StringBuilder();
				for(int i=0; i<checks.length; i++){
					if(checks[i]){
						modifiers.add(i);
						modifiersString.append(i).append(' ');
					}
				}
				diagramUpdater.setModifiers((PropertyValue)clickedItem, modifiers);
				accessibility.playSound(SoundEvent.V_OK);
				accessibility.speak("modifiers set for "+path.current());
				if(modifiersString.length() == 0)
					modifiersString.append("no modifiers");
				ILogger.log("modifiers set for "+path.current()+": "+ modifiersString.toString());
			}
			/* update the view */
			cachedList = buildCurrentChildList();
			updateable.update();
			dialogFragment.dismiss();
		}		
	}
	
	/**
	 * 
	 * Simple interface to provide an update method 
	 *
	 */
	interface Updateable {
		/**
		 * performs an update
		 */
		public void update();
	}
}