annotate 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
rev   line source
fiore@0 1 /*
fiore@0 2 CCmI Diagram Editor for Android
fiore@0 3
fiore@0 4 Copyright (C) 2012 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
fiore@0 5
fiore@0 6 This program is free software: you can redistribute it and/or modify
fiore@0 7 it under the terms of the GNU General Public License as published by
fiore@0 8 the Free Software Foundation, either version 3 of the License, or
fiore@0 9 (at your option) any later version.
fiore@0 10
fiore@0 11 This program is distributed in the hope that it will be useful,
fiore@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
fiore@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
fiore@0 14 GNU General Public License for more details.
fiore@0 15
fiore@0 16 You should have received a copy of the GNU General Public License
fiore@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
fiore@0 18 */
fiore@0 19 package uk.ac.qmul.eecs.ccmi.activities;
fiore@0 20
fiore@0 21 import java.util.ArrayList;
fiore@0 22 import java.util.LinkedHashMap;
fiore@0 23 import java.util.LinkedHashSet;
fiore@0 24 import java.util.LinkedList;
fiore@0 25 import java.util.List;
fiore@0 26 import java.util.Map;
fiore@0 27 import java.util.Set;
fiore@0 28
fiore@0 29 import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService;
fiore@0 30 import uk.ac.qmul.eecs.ccmi.accessibility.AccessibilityService.SoundEvent;
fiore@0 31 import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleCheckbox;
fiore@0 32 import uk.ac.qmul.eecs.ccmi.accessibility.AccessibleDialogBuilder;
fiore@1 33 import uk.ac.qmul.eecs.ccmi.utilities.ILogger;
fiore@0 34 import uk.ac.qmul.eecs.ccmi.utilities.Stack;
fiore@0 35 import uk.ac.qmul.eecs.ccmi.xmlparser.Diagram;
fiore@0 36 import uk.ac.qmul.eecs.ccmi.xmlparser.DiagramUpdater;
fiore@0 37 import uk.ac.qmul.eecs.ccmi.xmlparser.Edge;
fiore@0 38 import uk.ac.qmul.eecs.ccmi.xmlparser.EdgeNode;
fiore@0 39 import uk.ac.qmul.eecs.ccmi.xmlparser.EdgeType;
fiore@0 40 import uk.ac.qmul.eecs.ccmi.xmlparser.HierarchyItem;
fiore@0 41 import uk.ac.qmul.eecs.ccmi.xmlparser.LocalDiagramUpdater;
fiore@0 42 import uk.ac.qmul.eecs.ccmi.xmlparser.Modifier;
fiore@0 43 import uk.ac.qmul.eecs.ccmi.xmlparser.Node;
fiore@0 44 import uk.ac.qmul.eecs.ccmi.xmlparser.NodeProperty;
fiore@0 45 import uk.ac.qmul.eecs.ccmi.xmlparser.NodePropertyType;
fiore@0 46 import uk.ac.qmul.eecs.ccmi.xmlparser.NodeType;
fiore@0 47 import uk.ac.qmul.eecs.ccmi.xmlparser.PropertyValue;
fiore@0 48 import android.support.v4.app.DialogFragment;
fiore@0 49 import android.view.View;
fiore@0 50 import android.widget.AdapterView;
fiore@0 51 import android.widget.EditText;
fiore@0 52 import android.widget.Spinner;
fiore@0 53
fiore@0 54 /**
fiore@0 55 * Provides a navigation of the model (the diagram) based on the tree hierarchy of the
fiore@0 56 * <a href="http://ccmi.eecs.qmul.ac.uk">CCmI Diagram Editor</a>
fiore@0 57 *
fiore@0 58 */
fiore@0 59 class TreeNavigation {
fiore@0 60
fiore@0 61 private static final String RENAME_DIALOG_TAG = "Rename ";
fiore@0 62 private static final String EDIT_NODE_DIALOG_TAG = "Edit Node Dialog";
fiore@0 63 private static final String EDIT_EDGE_DIALOG_TAG = "Edit Edge Dialog";
fiore@0 64 private static final String EDIT_NODE_REF_DIALOG_TAG = "Edit Node Reference Dialog";
fiore@0 65 private static final String EDIT_LABEL_DIALOG_TAG = "Edit Label";
fiore@0 66 private static final String EDIT_ARROWHEAD_DIALOG_TAG = "Edit Arrow Head Dialog";
fiore@0 67 private static final String CONFIRMATION_DIALOG_TAG = "Are you sure you want to delete";
fiore@0 68 private static final String ADD_PROPERTY_DIALOG_TAG = "Add Property Dialog";
fiore@0 69 private static final String EDIT_PROPERTY_DIALOG_TAG = "Edit Property Dialog";
fiore@0 70 private static final String EDIT_MODIFIERS_DIALOG_TAG = "Edit Modifiers Dialog";
fiore@0 71
fiore@0 72 private Diagram diagram;
fiore@0 73 private DiagramUpdater diagramUpdater;
fiore@0 74 private Stack<HierarchyItem> path;
fiore@0 75
fiore@0 76 private List<Node> selectedNodes = new ArrayList<Node>(4);
fiore@0 77 /* cached list is filled up when next() and previous() and used to undo the navigation
fiore@0 78 * if the list created is empty. Once created, then it's returned by getCurrentList() */
fiore@0 79 private String[] cachedList;
fiore@0 80 private Stack<String> cachedHeaderTexts;
fiore@0 81
fiore@0 82 /* controller for edit actions with access to the current status of the navigation */
fiore@0 83 private Controller controller;
fiore@0 84 private Updateable updateable;
fiore@0 85
fiore@0 86 /* the level currently displayed. Level item's toString() is on the header *
fiore@0 87 * text and its children are displayed in the currentList */
fiore@0 88 private final static int PATH_MAX_LEN = 5;
fiore@0 89 public final static int DIAGRAM_LEVEL = 0;
fiore@0 90 public final static int TYPE_LEVEL = 1;
fiore@0 91 public final static int ITEM_LEVEL = 2;
fiore@0 92 public final static int REFERENCE_AND_PROPERTY_TYPE_LEVEL = 3;
fiore@0 93 public final static int PROPERTY_AND_EDGE_REFERENCE_LEVEL = 4; // neved used
fiore@0 94
fiore@0 95 public TreeNavigation(Diagram diagram, AccessibleDialogBuilder dialogBuilder, Updateable updateable){
fiore@0 96 this.diagram = diagram;
fiore@0 97 this.updateable = updateable;
fiore@0 98 diagramUpdater = new LocalDiagramUpdater(diagram);
fiore@0 99 path = new Stack<HierarchyItem>(PATH_MAX_LEN);
fiore@0 100 cachedHeaderTexts = new Stack<String>(PATH_MAX_LEN);
fiore@0 101 /* tree path starts with top node */
fiore@0 102 path.push(diagram);
fiore@0 103 cachedHeaderTexts.push(diagram.toString());
fiore@0 104 cachedList = buildCurrentChildList();
fiore@0 105 controller = new Controller(dialogBuilder);
fiore@0 106 }
fiore@0 107
fiore@0 108 public String[] getCurrentChildList(){
fiore@0 109 return cachedList;
fiore@0 110 }
fiore@0 111
fiore@0 112 public String getCurrentPath(){
fiore@0 113 StringBuilder builder = new StringBuilder();
fiore@0 114 for(HierarchyItem item : path){
fiore@0 115 builder.append('/').append(item);
fiore@0 116 }
fiore@0 117 return builder.toString();
fiore@0 118 }
fiore@0 119
fiore@0 120 /* builds the String list of items to display when getCurrentListIsCalled */
fiore@0 121 private String[] buildCurrentChildList(){
fiore@0 122 String[] currentList = null;
fiore@0 123 switch(path.level()){
fiore@0 124 case DIAGRAM_LEVEL :{
fiore@0 125 List<NodeType> nodeTypes = diagram.getPrototypes().getNodeTypes();
fiore@0 126 List<EdgeType> edgeTypes = diagram.getPrototypes().getEdgeTypes();
fiore@0 127 int numNodeTypes = nodeTypes.size();
fiore@0 128 int numEdgeTypes = edgeTypes.size();
fiore@0 129 currentList = new String[numNodeTypes+numEdgeTypes];
fiore@0 130
fiore@0 131 /* build the string for each node type */
fiore@0 132 StringBuilder builder = new StringBuilder();
fiore@0 133 for(int i=0; i<numNodeTypes;i++){
fiore@0 134 builder.setLength(0);
fiore@0 135 builder.append(nodeTypes.get(i).getType())
fiore@0 136 .append(' ')
fiore@0 137 .append('(')
fiore@0 138 .append(count(nodeTypes.get(i)))
fiore@0 139 .append(')');
fiore@0 140 currentList[i] = builder.toString();
fiore@0 141 }
fiore@0 142 /* build the string for each edge type */
fiore@0 143 for(int j=0; j<numEdgeTypes; j++){
fiore@0 144 builder.setLength(0);
fiore@0 145 builder.append(edgeTypes.get(j).getType())
fiore@0 146 .append(' ')
fiore@0 147 .append('(')
fiore@0 148 .append(count(edgeTypes.get(j)))
fiore@0 149 .append(')');
fiore@0 150 currentList[numNodeTypes+j] = builder.toString();
fiore@0 151 }
fiore@0 152
fiore@0 153 break;
fiore@0 154 }
fiore@0 155 case TYPE_LEVEL : {
fiore@0 156 /* list all objects of a certain type */
fiore@0 157 LinkedList<String> items = new LinkedList<String>();
fiore@0 158 if(path.current() instanceof NodeType){
fiore@0 159 for(Node cNode : diagram.getComponents().getNodes()){
fiore@0 160 if(path.current().toString().equals(cNode.getType()))
fiore@0 161 if(selectedNodes.contains(cNode))
fiore@0 162 items.add("<"+cNode+">"); // <selected node>
fiore@0 163 else
fiore@0 164 items.add(cNode.getName());
fiore@0 165 }
fiore@0 166 }else{
fiore@0 167 for(Edge cEdge : diagram.getComponents().getEdges()){
fiore@0 168 if(path.current().toString().equals(cEdge.getType()))
fiore@0 169 items.add(cEdge.getName());
fiore@0 170 }
fiore@0 171 }
fiore@0 172 currentList = items.toArray(new String[items.size()]);
fiore@0 173 break;
fiore@0 174 }
fiore@0 175 case ITEM_LEVEL : {
fiore@0 176 Map<String,Integer> edgeTypes = new LinkedHashMap<String,Integer>();
fiore@0 177 if(path.current() instanceof Node){
fiore@0 178 /* node displays the list of edge types connected to *
fiore@0 179 * itself + the list of property types defined for itself */
fiore@0 180 Node node = (Node)path.current();
fiore@0 181 /* add the edge types of the edges the node is attached to */
fiore@0 182 for(Edge cEdge : diagram.getComponents().getEdges()){
fiore@0 183 for(EdgeNode attachedNode : cEdge.getAttachedNodes()){
fiore@0 184 if(attachedNode.getId() == node.getId()){
fiore@0 185 if(!edgeTypes.containsKey(cEdge.getType())){
fiore@0 186 edgeTypes.put(cEdge.getType(),1);
fiore@0 187 }else{
fiore@0 188 edgeTypes.put(cEdge.getType(),edgeTypes.get(cEdge.getType())+1);
fiore@0 189 }
fiore@0 190 break;
fiore@0 191 }
fiore@0 192 }
fiore@0 193 }
fiore@0 194
fiore@0 195 /* build the array to return : first part is the edge types. second part property types /
fiore@0 196 /* now the property types this node can have */
fiore@0 197 currentList = new String[edgeTypes.size()+node.getProperties().size()];
fiore@0 198 int i = 0;
fiore@0 199 StringBuilder builder = new StringBuilder();
fiore@0 200 for(Map.Entry<String, Integer> entry : edgeTypes.entrySet()){
fiore@0 201 builder.setLength(0);
fiore@0 202 currentList[i++] = builder.append(entry.getKey())
fiore@0 203 .append(' ')
fiore@0 204 .append('(')
fiore@0 205 .append(entry.getValue())
fiore@0 206 .append(')').toString();
fiore@0 207 }
fiore@0 208
fiore@0 209 for(int j=i;j<currentList.length;j++){
fiore@0 210 builder.setLength(0);
fiore@0 211 currentList[j] = builder.append(node.getProperties().get(j-i).getType())
fiore@0 212 .append(' ')
fiore@0 213 .append('(')
fiore@0 214 .append(node.getProperties().get(j-i).getValues().size())
fiore@0 215 .append(')').toString();
fiore@0 216 }
fiore@0 217
fiore@0 218 }else{ // Edge
fiore@0 219 /* edge displays the list of nodes attached to self */
fiore@0 220 Edge cEdge = (Edge)path.current();
fiore@0 221 currentList = new String[cEdge.getAttachedNodes().size()];
fiore@0 222 int i = 0;
fiore@0 223 for(EdgeNode attachedNode : cEdge.getAttachedNodes()){
fiore@0 224 for(Node node : diagram.getComponents().getNodes()){
fiore@0 225 if(node.getId() == attachedNode.getId()){
fiore@0 226 currentList[i++] = (attachedNode.getHead()+" "+node.getName()+" "+attachedNode.getLabel()).trim();
fiore@0 227 }
fiore@0 228 }
fiore@0 229 }
fiore@0 230 }
fiore@0 231 break;
fiore@0 232 }
fiore@0 233 case REFERENCE_AND_PROPERTY_TYPE_LEVEL : {
fiore@0 234 /* display the values of the selected property types */
fiore@0 235 if(path.current() instanceof NodeProperty){ // property type is displayed on the header
fiore@0 236 NodeProperty property = (NodeProperty)path.current();
fiore@0 237 currentList = new String[property.getValues().size()];
fiore@0 238 NodeType nodeType = (NodeType)path.get(TYPE_LEVEL);
fiore@0 239
fiore@0 240 /* for each property value build the string "mod1 mod2 mod3...value" *
fiore@0 241 * where modn is the modifier type and value is the property value */
fiore@0 242 StringBuilder builder = new StringBuilder();
fiore@0 243 for(int i=0; i<currentList.length; i++){
fiore@0 244 builder.setLength(0);
fiore@0 245 List<Integer> modifierIndexes = property.getValues().get(i).getModifiersIndexes();
fiore@0 246 for(NodePropertyType propertyType : nodeType.getPropretyTypes()){
fiore@0 247 if(propertyType.getType().equals(property.getType())){
fiore@0 248 ArrayList<Modifier> modifiers = propertyType.getModifiers();
fiore@0 249 for(Integer j : modifierIndexes){
fiore@0 250 builder.append(modifiers.get(j).getType()).append(' ');
fiore@0 251 }
fiore@0 252 }
fiore@0 253 }
fiore@0 254 currentList[i] = builder.append(property.getValues().get(i).getValue()).toString();
fiore@0 255 }
fiore@0 256
fiore@0 257 // NodeProperty propertyType = (NodeProperty)path.current();
fiore@0 258 // Node node = (Node)path.get(ITEM_LEVEL);
fiore@0 259 // for(NodeProperty property : node.getProperties()){
fiore@0 260 // if(property.getType().equals(propertyType.getType())){
fiore@0 261 // currentList = new String[property.getValues().size()];
fiore@0 262 // for(int i=0; i<currentList.length; i++){
fiore@0 263 // currentList[i] = property.getValues().get(i).getValue();
fiore@0 264 // }
fiore@0 265 // break;
fiore@0 266 // }
fiore@0 267 // }
fiore@0 268 }else{ // instance of EdgeType
fiore@0 269 EdgeType edgeType = (EdgeType)path.current();
fiore@0 270 LinkedList<String> currentListItems = new LinkedList<String>();
fiore@0 271 int nodeID = ((Node)path.get(ITEM_LEVEL)).getId();
fiore@0 272 StringBuilder builder = new StringBuilder();
fiore@0 273 for(Edge edge : diagram.getComponents().getEdges()){
fiore@0 274 if(edge.getType().equals(edgeType.getType())){
fiore@0 275 for(EdgeNode attachedNode : edge.getAttachedNodes()){
fiore@0 276 if(attachedNode.getId() == nodeID){
fiore@0 277 currentListItems.add(makeEdgeReferenceString(edge,nodeID,builder));
fiore@0 278 }
fiore@0 279 }
fiore@0 280 }
fiore@0 281 }
fiore@0 282 currentList = currentListItems.toArray(new String[currentListItems.size()]);
fiore@0 283 }
fiore@0 284 break;
fiore@0 285 }
fiore@0 286 default : new IllegalStateException("Wrong path level: "+path.level());
fiore@0 287 }
fiore@0 288 return currentList;
fiore@0 289 }
fiore@0 290
fiore@0 291 /**
fiore@0 292 * Returns the label of the parent of the list currently visualized
fiore@0 293 *
fiore@0 294 * @return the label of the parent
fiore@0 295 */
fiore@0 296 public String getCurrentItemName(){
fiore@0 297 String text = cachedHeaderTexts.current();
fiore@0 298 if(text != null)
fiore@0 299 return text;
fiore@0 300 else
fiore@0 301 return path.current().toString();
fiore@0 302 }
fiore@0 303
fiore@0 304 /**
fiore@0 305 * Returns the level of the navigation at the moment the method is called.
fiore@0 306 *
fiore@0 307 * @return an {@code int} representing the current level. Possible values are
fiore@0 308 * listed as {@code static} variables of this class.
fiore@0 309 */
fiore@0 310 public int getCurrentLevel(){
fiore@0 311 return path.level();
fiore@0 312 }
fiore@0 313
fiore@0 314 /**
fiore@0 315 * Makes a child of the current {@code HierarchyItem} the current one.
fiore@0 316 *
fiore@0 317 * @param child the index of the child becoming the current
fiore@0 318 * @return {@code true} if the current position has changed after the call
fiore@0 319 **/
fiore@0 320 public boolean goNext(int child){
fiore@0 321 if(child < 0)
fiore@0 322 throw new IllegalArgumentException("Wrong child index: "+child);
fiore@0 323
fiore@0 324 HierarchyItem hierarchyItem = findNext(child);
fiore@0 325 if(hierarchyItem == null)
fiore@0 326 return false;
fiore@0 327
fiore@0 328 path.push(hierarchyItem);
fiore@0 329 /* saves the selected list item to display as a title on the next call to getCurrentHeaderText() */
fiore@0 330 cachedHeaderTexts.push(cachedList[child]);
fiore@0 331 /* make the new list to return for next calls to getCurrentList() */
fiore@0 332 cachedList = buildCurrentChildList();
fiore@0 333 /* the selected hierarchy node has no children. go back and return false */
fiore@0 334 if(cachedList.length == 0){
fiore@0 335 goPrevious();
fiore@0 336 return false;
fiore@0 337 }
fiore@0 338 updateable.update();
fiore@0 339 return true;
fiore@0 340 }
fiore@0 341
fiore@0 342 private HierarchyItem findNext(int child){
fiore@0 343 switch(path.level()){
fiore@0 344 case DIAGRAM_LEVEL :{
fiore@0 345 List<NodeType> nodeTypes = diagram.getPrototypes().getNodeTypes();
fiore@0 346 List<EdgeType> edgeTypes = diagram.getPrototypes().getEdgeTypes();
fiore@0 347 int numNodeTypes = nodeTypes.size();
fiore@0 348 int numEdgeTypes = edgeTypes.size();
fiore@0 349 if(child >= numNodeTypes+numEdgeTypes)
fiore@0 350 throw new IllegalArgumentException("Wrong child index: "+child);
fiore@0 351
fiore@0 352 if(child < numNodeTypes){
fiore@0 353 return nodeTypes.get(child);
fiore@0 354 }else{
fiore@0 355 return edgeTypes.get(child-numNodeTypes);
fiore@0 356 }
fiore@0 357 }
fiore@0 358 case TYPE_LEVEL : {
fiore@0 359 int numTypeItems = 0;// the number of nodes or edges found of the current type
fiore@0 360 if(path.current() instanceof NodeType){
fiore@0 361 /* find the child-th node of the current type */
fiore@0 362 for(Node node : diagram.getComponents().getNodes()){
fiore@0 363 if(path.current().toString().equals(node.getType())){
fiore@0 364 if(numTypeItems == child){
fiore@0 365 return node;
fiore@0 366 }else{
fiore@0 367 numTypeItems++;
fiore@0 368 }
fiore@0 369 }
fiore@0 370 }
fiore@0 371 }else{ // EdgeType
fiore@0 372 for(Edge edge : diagram.getComponents().getEdges()){
fiore@0 373 /* find the child-th edge of the current type */
fiore@0 374 if(path.current().toString().equals(edge.getType())){
fiore@0 375 if(numTypeItems == child){
fiore@0 376 return edge;
fiore@0 377 }else{
fiore@0 378 numTypeItems++;
fiore@0 379 }
fiore@0 380 }
fiore@0 381 }
fiore@0 382 }
fiore@0 383 break;
fiore@0 384 }
fiore@0 385 case ITEM_LEVEL : {
fiore@0 386 /* we can go further only for nodes */
fiore@0 387 if(path.current() instanceof Node){
fiore@0 388 Node node = (Node)path.current();
fiore@0 389 /* check if user clicked on an edge, this node is attached to */
fiore@0 390
fiore@0 391 Set<String> cNodeEdgeTypes = new LinkedHashSet<String>();
fiore@0 392 /* for each edge component */
fiore@0 393 for(Edge edge : diagram.getComponents().getEdges()){
fiore@0 394 /* for each attached node to this edge */
fiore@0 395 for(EdgeNode attachedNode : edge.getAttachedNodes()){
fiore@0 396 /* if it's this node we're looking for (cNode) */
fiore@0 397 if(attachedNode.getId() == node.getId()){
fiore@0 398 cNodeEdgeTypes.add(edge.getType());
fiore@0 399 }
fiore@0 400 }
fiore@0 401 }
fiore@0 402
fiore@0 403 if(child < cNodeEdgeTypes.size()){
fiore@0 404 String selectedType = cNodeEdgeTypes.toArray(new String[cNodeEdgeTypes.size()])[child];
fiore@0 405 for(EdgeType edge : diagram.getPrototypes().getEdgeTypes()){
fiore@0 406 if(edge.getType().equals(selectedType)){
fiore@0 407 return edge;
fiore@0 408 }
fiore@0 409 }
fiore@0 410 }else{// user selected a NodePropertyType put a NodeProperty in the path
fiore@0 411 return node.getProperties().get(child-cNodeEdgeTypes.size());
fiore@0 412 }
fiore@0 413 break;
fiore@0 414 }
fiore@0 415 }
fiore@0 416 default : return null; // don't go further than REFERENCE_AND_PROPERTY_LEVEL
fiore@0 417 }
fiore@0 418 return null;
fiore@0 419 }
fiore@0 420
fiore@0 421 /**
fiore@0 422 * Makes the father of the current {@code HierarchyItem} the current one.
fiore@0 423 * @return @code true} if the current position has changed after the call
fiore@0 424 */
fiore@0 425 public boolean goPrevious(){
fiore@0 426 if(path.level() == DIAGRAM_LEVEL) // level = DIAGRAM_LEVEL
fiore@0 427 return false;
fiore@0 428 path.pop();
fiore@0 429 cachedList = buildCurrentChildList();
fiore@0 430 cachedHeaderTexts.pop();
fiore@0 431 return true;
fiore@0 432 }
fiore@0 433
fiore@0 434 /**
fiore@0 435 * Returns a controller, that can be used to
fiore@0 436 * change the model (the diagram) in a way coherent to the tree navigation
fiore@0 437 *
fiore@0 438 * @return a controller connected to this navigation
fiore@0 439 */
fiore@0 440 public Controller getController(){
fiore@0 441 return controller;
fiore@0 442 }
fiore@0 443
fiore@0 444 /* Counts the number of nodes or edges of a given type */
fiore@0 445 private int count(HierarchyItem hItem){
fiore@0 446 int count = 0;
fiore@0 447 if(hItem instanceof NodeType){
fiore@0 448 NodeType node = (NodeType)hItem;
fiore@0 449 for(Node cNode : diagram.getComponents().getNodes())
fiore@0 450 if(cNode.getType().equals(node.getType()))
fiore@0 451 count++;
fiore@0 452 }else if(hItem instanceof EdgeType){
fiore@0 453 EdgeType edge = (EdgeType)hItem;
fiore@0 454 for(Edge cEdge : diagram.getComponents().getEdges())
fiore@0 455 if(cEdge.getType().equals(edge.getType()))
fiore@0 456 count++;
fiore@0 457 }
fiore@0 458 return count;
fiore@0 459 }
fiore@0 460
fiore@0 461 /* Creates a string for the node reference of edge related to the node N equal to ID nodeID.
fiore@0 462 * The string looks like "to N1, N2 and N3 vie E", where N1, N2 and N3 are the nodes connected
fiore@0 463 * to N via E ( they can be more than one in case of a multiple ended edge ).
fiore@0 464 */
fiore@0 465 private String makeEdgeReferenceString(Edge edge, int nodeID, StringBuilder builder) {
fiore@0 466 builder.setLength(0);
fiore@0 467 builder.append("to ");
fiore@0 468 /* attach the name of the nodes interleaved by " and " */
fiore@0 469 for(int i=0; i<edge.getAttachedNodes().size(); i++){
fiore@0 470 EdgeNode attachedNode = edge.getAttachedNodes().get(i);
fiore@0 471 if(attachedNode.getId() != nodeID){
fiore@0 472 for(Node cNode : diagram.getComponents().getNodes()){
fiore@0 473 if(cNode.getId() == attachedNode.getId()){
fiore@0 474 if(i == edge.getAttachedNodes().size()-1){
fiore@0 475 builder.append(cNode.getName());
fiore@0 476 }else if(i == edge.getAttachedNodes().size()-2){
fiore@0 477 builder.append(cNode.getName()).append(" and ");
fiore@0 478 }else{
fiore@0 479 builder.append(cNode.getName()).append(", ");
fiore@0 480 }
fiore@0 481 break;
fiore@0 482 }
fiore@0 483 }
fiore@0 484 }
fiore@0 485 }
fiore@0 486 builder.append(", via ");
fiore@0 487 builder.append(edge.getName());
fiore@0 488 return builder.toString();
fiore@0 489 }
fiore@0 490
fiore@0 491 /* A controller that changes the model (the data structures in the xml parser package) using the informations *
fiore@0 492 * given by the TreeNavigation. When commands are issued by the user (through long click) the controller pops *
fiore@0 493 * up a dialog according to what the user chose and which layer of the tree they are currently visualizing *
fiore@0 494 * if the user issues the command (doesn't cancel the dialog) the model is updated and the updater *
fiore@0 495 * is called to refresh and visualize the result */
fiore@0 496 class Controller implements AccessibleDialogBuilder.ButtonClickListener {
fiore@0 497 AccessibleDialogBuilder dialogBuilder;
fiore@0 498 HierarchyItem clickedItem;
fiore@0 499
fiore@0 500 Controller(AccessibleDialogBuilder dialogBuilder){
fiore@0 501 this.dialogBuilder = dialogBuilder;
fiore@0 502 };
fiore@0 503
fiore@0 504 /*
fiore@1 505 * Responds to the long click of the user. It acts according to the list item clicked
fiore@0 506 * by the user (and therefore according to the hierarchy level currently displayed).
fiore@0 507 * When a dialog needs to be shown, it used dialogBuilder.displayDialog() and register itself
fiore@0 508 * as buttoClickListener to handle the click of the user in the same class.
fiore@0 509 */
fiore@0 510 public boolean performEditAction(AdapterView<?> parent, View view,
fiore@0 511 int position, long id, Updateable u) {
fiore@0 512 switch(getCurrentLevel()){
fiore@0 513 case TreeNavigation.DIAGRAM_LEVEL : {
fiore@0 514 List<NodeType> nodeTypes = diagram.getPrototypes().getNodeTypes();
fiore@0 515 if(position < nodeTypes.size()){ // user clicked on a node
fiore@0 516 clickedItem = nodeTypes.get(position);
fiore@0 517 /* build the properties for node constructor using node property types */
fiore@0 518 NodeType nodeType = nodeTypes.get(position);
fiore@0 519 ArrayList<NodeProperty> properties = new ArrayList<NodeProperty>(nodeType.getPropretyTypes().size());
fiore@0 520 for(NodePropertyType pType : nodeType.getPropretyTypes()){
fiore@0 521 properties.add(new NodeProperty(pType.getType()));
fiore@0 522 }
fiore@0 523 /* construct the node and add it to the diagram */
fiore@0 524 Node node = new Node(((NodeType)clickedItem).getType(),properties);
fiore@0 525 diagramUpdater.addNode(node);
fiore@1 526 dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_OK);
fiore@0 527 dialogBuilder.getAccessibilityService().speak("Node "+ node +" created");
fiore@1 528 ILogger.log("Node "+ node +" created");
fiore@0 529 }else{ // user clicked on edge
fiore@0 530 List<EdgeType> edgeTypes = diagram.getPrototypes().getEdgeTypes();
fiore@0 531 clickedItem = edgeTypes.get(position - nodeTypes.size());
fiore@0 532 /* respect min and max attached nodes */
fiore@0 533 if(selectedNodes.size() < ((EdgeType)clickedItem).getMinAttachedNodes()){
fiore@1 534 dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_ERROR);
fiore@0 535 dialogBuilder.getAccessibilityService().speak("you must select at least "+
fiore@0 536 ((EdgeType)clickedItem).getMinAttachedNodes()+" nodes");
fiore@1 537 ILogger.logError("selected nodes < "+((EdgeType)clickedItem).getMinAttachedNodes());
fiore@0 538 return true;
fiore@0 539 }
fiore@0 540 if(selectedNodes.size() > ((EdgeType)clickedItem).getMaxAttachedNodes()){
fiore@1 541 dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_ERROR);
fiore@0 542 dialogBuilder.getAccessibilityService().speak("you must select at most "+
fiore@0 543 ((EdgeType)clickedItem).getMaxAttachedNodes()+" nodes");
fiore@1 544 ILogger.logError("selected nodes > "+((EdgeType)clickedItem).getMaxAttachedNodes());
fiore@0 545 return true;
fiore@0 546 }
fiore@0 547
fiore@0 548 /* create the edge and add the edge nodes */
fiore@0 549 Edge edge = new Edge(((EdgeType)clickedItem).getType());
fiore@0 550 edge.getAttachedNodes().clear();
fiore@0 551 /* create all the node references (EdgeNode) for this edge */
fiore@0 552 for(Node n : diagram.getComponents().getNodes()){
fiore@0 553 if(selectedNodes.contains(n))
fiore@0 554 edge.getAttachedNodes().add(new EdgeNode(n.getId()));
fiore@0 555 }
fiore@0 556 diagramUpdater.addEdge(edge);
fiore@1 557 dialogBuilder.getAccessibilityService().playSound(SoundEvent.V_OK);
fiore@0 558 StringBuilder builder = new StringBuilder();
fiore@0 559 builder.append(edge).append(" created between ");
fiore@0 560 for(int i=0; i<selectedNodes.size(); i++){
fiore@0 561 Node sn = selectedNodes.get(i);
fiore@0 562 builder.append(sn.getName());
fiore@0 563 if(i == selectedNodes.size() - 2 ){
fiore@0 564 builder.append(", and ");
fiore@0 565 }else if (i != selectedNodes.size() - 1 ){
fiore@0 566 builder.append(", ");
fiore@0 567 }
fiore@0 568 }
fiore@0 569 selectedNodes.clear(); // when an edge is added the selected node are cleared
fiore@0 570 dialogBuilder.getAccessibilityService().speak(builder.toString());
fiore@1 571 ILogger.log(builder.toString());
fiore@0 572 }
fiore@0 573 /* update the view */
fiore@0 574 cachedList = buildCurrentChildList();
fiore@0 575 updateable.update();
fiore@0 576 return true;
fiore@0 577 }
fiore@0 578 case TreeNavigation.TYPE_LEVEL :{
fiore@0 579 clickedItem = findNext(position);
fiore@0 580 /* path.current() is what's displayed on the header */
fiore@0 581 if(path.current() instanceof NodeType){ // Node
fiore@0 582 if(selectedNodes.contains(clickedItem)){
fiore@0 583 dialogBuilder.displayDialog(R.layout.alert_dialog_edit_node_unselect, EDIT_NODE_DIALOG_TAG, this);
fiore@0 584 }else{
fiore@0 585 dialogBuilder.displayDialog(R.layout.alert_dialog_edit_node, EDIT_NODE_DIALOG_TAG, this);
fiore@0 586 }
fiore@0 587 }else{ // Edge
fiore@0 588 dialogBuilder.displayDialog(R.layout.alert_dialog_edit_edge, EDIT_EDGE_DIALOG_TAG, this);
fiore@0 589 }
fiore@0 590 return true;
fiore@0 591 }
fiore@0 592 case TreeNavigation.ITEM_LEVEL :{
fiore@0 593 if(path.current() instanceof Node){ // Node
fiore@0 594 clickedItem = findNext(position);
fiore@0 595 /* edge types inside node cannot be edited directly */
fiore@0 596 if(clickedItem instanceof EdgeType)
fiore@0 597 return false;
fiore@0 598 /* if it ain't an EdgeType then t's a PropertyType */
fiore@0 599 /* property type items can be used to add new properties */
fiore@0 600 dialogBuilder.displayDialog(R.layout.alert_dialog_add_property, ADD_PROPERTY_DIALOG_TAG, this);
fiore@0 601 }else{ // Edge
fiore@0 602 clickedItem = ((Edge)path.current()).getAttachedNodes().get(position);
fiore@0 603 dialogBuilder.displayDialog(R.layout.alert_dialog_edit_node_reference, EDIT_NODE_REF_DIALOG_TAG, this);
fiore@0 604 }
fiore@0 605 return true;
fiore@0 606 }
fiore@0 607 case TreeNavigation.REFERENCE_AND_PROPERTY_TYPE_LEVEL :{
fiore@0 608 if(path.current() instanceof NodeProperty){
fiore@0 609 NodeProperty nodeProperty = (NodeProperty)path.current();
fiore@0 610 clickedItem = nodeProperty.getValues().get(position);
fiore@0 611 dialogBuilder.displayDialog(R.layout.alert_dialog_edit_property, EDIT_PROPERTY_DIALOG_TAG, this);
fiore@0 612 return true;
fiore@0 613 }
fiore@0 614 return false;
fiore@0 615 }
fiore@0 616 case TreeNavigation.PROPERTY_AND_EDGE_REFERENCE_LEVEL :{
fiore@0 617 return false;// never happens
fiore@0 618 }
fiore@0 619 }
fiore@0 620 return false;
fiore@0 621 }
fiore@0 622
fiore@1 623 /* this is the callback triggered when the user clicks on any botton of the dialog shown. v is the button
fiore@0 624 * The method first checks for the dialog tag to understand which dialog it's handling, then it checks
fiore@0 625 * for the button tag to understand which button the user pressed. The first check though is on "CANCEL"
fiore@0 626 * button as its tag it's the same for all the dialog */
fiore@0 627 @Override
fiore@0 628 public void onClick(View v, DialogFragment dialogFragment, String dialogTag) {
fiore@0 629 Object buttonTag = v.getTag();
fiore@1 630 ILogger.logButton(buttonTag.toString());
fiore@0 631 AccessibilityService accessibility = dialogBuilder.getAccessibilityService();
fiore@0 632
fiore@0 633 if("CANCEL".equals(buttonTag)){
fiore@1 634 accessibility.playSound(SoundEvent.V_CANCEL);
fiore@0 635 accessibility.speak("Cancel");
fiore@0 636 dialogFragment.dismiss();
fiore@0 637 return;
fiore@0 638 }
fiore@0 639
fiore@0 640 if(EDIT_NODE_DIALOG_TAG.equals(dialogTag) || EDIT_EDGE_DIALOG_TAG.equals(dialogTag)){
fiore@0 641 if("SELECT".equals(buttonTag)){
fiore@0 642 selectedNodes.add((Node)clickedItem);
fiore@0 643 accessibility.playSound(SoundEvent.V_OK);
fiore@0 644 accessibility.speak(clickedItem + " selected");
fiore@1 645 ILogger.log(clickedItem + " selected");
fiore@0 646 }else if("UNSELECT".equals(buttonTag)){
fiore@0 647 selectedNodes.remove(clickedItem);
fiore@0 648 accessibility.playSound(SoundEvent.V_OK);
fiore@0 649 accessibility.speak(clickedItem + " unselected");
fiore@1 650 ILogger.log(clickedItem + " unselected");
fiore@0 651 }else if("RENAME".equals(buttonTag)){
fiore@0 652 dialogFragment.dismiss();
fiore@0 653 dialogBuilder.displayDialog(R.layout.alert_dialog_rename, RENAME_DIALOG_TAG+clickedItem, this);
fiore@0 654 }else if("DELETE".equals(buttonTag)){
fiore@0 655 dialogFragment.dismiss();
fiore@0 656 dialogBuilder.displayDialog(R.layout.alert_dialog_confirmation,
fiore@0 657 CONFIRMATION_DIALOG_TAG+
fiore@0 658 (EDIT_NODE_DIALOG_TAG.equals(dialogTag) ? " Node " : " Edge ") +
fiore@0 659 clickedItem,
fiore@0 660 this);
fiore@0 661 }
fiore@0 662 }else if(dialogTag.startsWith(RENAME_DIALOG_TAG)){
fiore@0 663 /* if it reached this point it'a "RENAME" button as "CANCEL" *
fiore@0 664 * have been matched against at the beginning of the method */
fiore@0 665 EditText editText = (EditText)dialogFragment.getDialog().findViewById(R.id.text_edit);
fiore@0 666 String text = editText.getText().toString().trim();
fiore@0 667 if(text.length() == 0){
fiore@0 668 accessibility.playSound(SoundEvent.V_ERROR);
fiore@0 669 accessibility.speak("Text cannot be empty");
fiore@1 670 ILogger.logError("text empty");
fiore@0 671 return;
fiore@0 672 }
fiore@0 673 String oldName = clickedItem.toString();
fiore@0 674 diagramUpdater.rename(clickedItem,text);
fiore@0 675 accessibility.playSound(SoundEvent.V_OK);
fiore@0 676 accessibility.speak(oldName+" renamed to "+clickedItem);
fiore@1 677 ILogger.log(oldName+" renamed to "+clickedItem);
fiore@0 678 }else if(dialogTag.startsWith(CONFIRMATION_DIALOG_TAG)){
fiore@0 679 /* if it reaches this point it's a "YES" as a "NO" button has "CANCEL" as its tag */
fiore@0 680 /* and the match against "CANCEL" match is performed first of all */
fiore@0 681 diagramUpdater.delete(clickedItem);
fiore@0 682 /* if it's a selected node, remove it from selected */
fiore@0 683 selectedNodes.remove(clickedItem);
fiore@1 684 accessibility.playSound(SoundEvent.V_OK);
fiore@0 685 accessibility.speak(clickedItem+" Deleted");
fiore@1 686 ILogger.log(clickedItem+" Deleted");
fiore@0 687 /* update the headers which show the number of children the current item contains */
fiore@0 688 if(path.current() instanceof NodeType || path.current() instanceof EdgeType){
fiore@0 689 cachedHeaderTexts.pop();
fiore@0 690 cachedHeaderTexts.push(path.current()+" ("+count(path.current())+')');
fiore@0 691 }else if(path.current() instanceof NodeProperty){
fiore@0 692 cachedHeaderTexts.pop();
fiore@0 693 cachedHeaderTexts.push(path.current()+" ("+((NodeProperty)path.current()).getValues().size()+')');
fiore@0 694 }
fiore@0 695 }else if(ADD_PROPERTY_DIALOG_TAG.equals(dialogTag)){
fiore@0 696 /* if it reaches this point it's a "CREATE" as "CANCEL" would *
fiore@0 697 * have been matched against at the beginning of the method */
fiore@0 698 EditText editText = (EditText)dialogFragment.getDialog().findViewById(R.id.text_edit);
fiore@0 699 String text = editText.getText().toString().trim();
fiore@0 700 if(text.length() == 0){
fiore@0 701 accessibility.playSound(SoundEvent.V_ERROR);
fiore@0 702 accessibility.speak("Text cannot be empty");
fiore@1 703 ILogger.logError("text empty");
fiore@0 704 return;
fiore@0 705 }
fiore@0 706 diagramUpdater.addProperty((Node)path.get(ITEM_LEVEL),((NodeProperty)clickedItem),text);
fiore@0 707 accessibility.playSound(SoundEvent.V_OK);
fiore@0 708 accessibility.speak("Property "+text+" added");
fiore@1 709 ILogger.log("Property "+text+" added");
fiore@0 710 }else if(EDIT_NODE_REF_DIALOG_TAG.equals(dialogTag)){
fiore@0 711 if("EDIT_LABEL".equals(buttonTag)){
fiore@0 712 dialogFragment.dismiss();
fiore@0 713 dialogBuilder.displayDialog(R.layout.alert_dialog_rename, EDIT_LABEL_DIALOG_TAG, this);
fiore@0 714 }else{ // EDIT_ARROWHEAD
fiore@0 715 dialogFragment.dismiss();
fiore@0 716 Edge edge = (Edge)path.current(); // get the edge this node reference belongs to
fiore@0 717 String[] heads = null;
fiore@0 718 /* build a string array with all the available head labels for this edge's type */
fiore@0 719 for(EdgeType edgeType : diagram.getPrototypes().getEdgeTypes()){
fiore@0 720 if(edgeType.getType().equals(edge.getType())){
fiore@0 721 heads = new String[edgeType.getHeads().size()];
fiore@0 722 for(int i=0; i<heads.length;i++)
fiore@0 723 heads[i] = edgeType.getHeads().get(i).getHeadLabel();
fiore@0 724 break;
fiore@0 725 }
fiore@0 726 }
fiore@0 727 if(heads == null || heads.length == 0){
fiore@0 728 accessibility.playSound(SoundEvent.V_ERROR);
fiore@0 729 accessibility.speak("There are no arrow heads defined for "+edge);
fiore@1 730 ILogger.logError("There are no arrow heads defined for "+edge);
fiore@0 731 return;
fiore@0 732 }
fiore@0 733 dialogBuilder.displaySelectionDialog(EDIT_ARROWHEAD_DIALOG_TAG, heads, this);
fiore@0 734 }
fiore@0 735 }else if(EDIT_LABEL_DIALOG_TAG.equals(dialogTag)){
fiore@0 736 EditText editText = (EditText)dialogFragment.getDialog().findViewById(R.id.text_edit);
fiore@0 737 String text = editText.getText().toString().trim();
fiore@0 738 if(text.length() == 0){
fiore@0 739 accessibility.playSound(SoundEvent.V_ERROR);
fiore@0 740 accessibility.speak("Text cannot be empty");
fiore@1 741 ILogger.logError("Text empty");
fiore@0 742 return;
fiore@0 743 }
fiore@0 744 diagramUpdater.setLabel((EdgeNode)clickedItem,text);
fiore@0 745 accessibility.playSound(SoundEvent.V_OK);
fiore@0 746 accessibility.speak("Label set to "+clickedItem);// EdgeNode.toString = EdgeNode.getLabel
fiore@1 747 ILogger.log("Label set to "+clickedItem);
fiore@0 748 }else if(EDIT_ARROWHEAD_DIALOG_TAG.equals(dialogTag)){
fiore@0 749 Spinner spinner = (Spinner)dialogFragment.getDialog().findViewById(R.id.selectionSpinner);
fiore@1 750 diagramUpdater.setArrowHead((EdgeNode)clickedItem, spinner.getSelectedItem().toString());
fiore@0 751 accessibility.playSound(SoundEvent.V_OK);
fiore@0 752 accessibility.speak("Arrow head set to "+spinner.getSelectedItem().toString());
fiore@1 753 ILogger.log("Arrow head set to "+spinner.getSelectedItem().toString());
fiore@0 754 }else if(EDIT_PROPERTY_DIALOG_TAG.equals(dialogTag)){
fiore@0 755 if("RENAME".equals(buttonTag)){
fiore@0 756 dialogFragment.dismiss();
fiore@0 757 dialogBuilder.displayDialog(R.layout.alert_dialog_rename, RENAME_DIALOG_TAG, this);
fiore@0 758 }else if("DELETE".equals(buttonTag)){
fiore@0 759 dialogFragment.dismiss();
fiore@0 760 dialogBuilder.displayDialog(R.layout.alert_dialog_confirmation,
fiore@0 761 CONFIRMATION_DIALOG_TAG+ " property "+clickedItem,
fiore@0 762 this);
fiore@0 763 }else if("MODIFIERS".equals(buttonTag)){
fiore@0 764 dialogFragment.dismiss();
fiore@0 765 /* get the modifiers from the NodePropertyType of the node where this property is */
fiore@0 766 NodeProperty property = (NodeProperty)path.current();
fiore@0 767 String[] modifiers = null;
fiore@0 768 boolean[] checks = null;
fiore@0 769 for( NodePropertyType propertyType : ((NodeType)path.get(TYPE_LEVEL)).getPropretyTypes()){
fiore@0 770 if(propertyType.getType().equals(property.getType())){
fiore@0 771 modifiers = new String[propertyType.getModifiers().size()];
fiore@0 772 checks = new boolean[modifiers.length];
fiore@0 773 for(int i=0; i< modifiers.length; i++){
fiore@0 774 modifiers[i] = propertyType.getModifiers().get(i).getType();
fiore@0 775 if(((PropertyValue)clickedItem).getModifiersIndexes().contains(i))
fiore@0 776 checks[i] = true;
fiore@0 777 }
fiore@0 778 }
fiore@0 779 }
fiore@0 780 if(modifiers == null || modifiers.length == 0){
fiore@0 781 accessibility.playSound(SoundEvent.V_ERROR);
fiore@0 782 accessibility.speak("There are no modifiers defined for "+property);
fiore@0 783 return;
fiore@0 784 }
fiore@0 785 dialogBuilder.displayCheckDialog(EDIT_MODIFIERS_DIALOG_TAG, modifiers, checks, this);
fiore@0 786 }
fiore@0 787 }else if(EDIT_MODIFIERS_DIALOG_TAG.equals(dialogTag)){
fiore@0 788 /* pressed button is OK as CANCEL would have been cought at the beginning */
fiore@0 789 AccessibleCheckbox checkbox = (AccessibleCheckbox)dialogFragment.getDialog().findViewById(R.id.checkBox);
fiore@0 790 boolean[] checks = checkbox.getChecks();
fiore@0 791 List<Integer> modifiers = new ArrayList<Integer>(checks.length);
fiore@1 792 StringBuilder modifiersString = new StringBuilder();
fiore@0 793 for(int i=0; i<checks.length; i++){
fiore@1 794 if(checks[i]){
fiore@0 795 modifiers.add(i);
fiore@1 796 modifiersString.append(i).append(' ');
fiore@1 797 }
fiore@0 798 }
fiore@0 799 diagramUpdater.setModifiers((PropertyValue)clickedItem, modifiers);
fiore@1 800 accessibility.playSound(SoundEvent.V_OK);
fiore@1 801 accessibility.speak("modifiers set for "+path.current());
fiore@1 802 if(modifiersString.length() == 0)
fiore@1 803 modifiersString.append("no modifiers");
fiore@1 804 ILogger.log("modifiers set for "+path.current()+": "+ modifiersString.toString());
fiore@0 805 }
fiore@0 806 /* update the view */
fiore@0 807 cachedList = buildCurrentChildList();
fiore@0 808 updateable.update();
fiore@0 809 dialogFragment.dismiss();
fiore@0 810 }
fiore@0 811 }
fiore@0 812
fiore@0 813 /**
fiore@0 814 *
fiore@0 815 * Simple interface to provide an update method
fiore@0 816 *
fiore@0 817 */
fiore@0 818 interface Updateable {
fiore@0 819 /**
fiore@0 820 * performs an update
fiore@0 821 */
fiore@0 822 public void update();
fiore@0 823 }
fiore@0 824 }