annotate java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java @ 1:e3935c01cde2 tip

moved license of PdPersistenceManager to the beginning of the file
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 19:52:03 +0100
parents 78b7fc5391a2
children
rev   line source
f@0 1 /*
f@0 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0 3
f@0 4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0 5
f@0 6 This program is free software: you can redistribute it and/or modify
f@0 7 it under the terms of the GNU General Public License as published by
f@0 8 the Free Software Foundation, either version 3 of the License, or
f@0 9 (at your option) any later version.
f@0 10
f@0 11 This program is distributed in the hope that it will be useful,
f@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0 14 GNU General Public License for more details.
f@0 15
f@0 16 You should have received a copy of the GNU General Public License
f@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@0 18 */
f@0 19 package uk.ac.qmul.eecs.ccmi.gui;
f@0 20
f@0 21 import java.awt.event.ActionEvent;
f@0 22 import java.awt.event.InputEvent;
f@0 23 import java.awt.event.KeyEvent;
f@0 24 import java.awt.event.MouseEvent;
f@0 25 import java.io.InputStream;
f@0 26 import java.text.MessageFormat;
f@0 27 import java.util.ArrayList;
f@0 28 import java.util.List;
f@0 29 import java.util.ResourceBundle;
f@0 30
f@0 31 import javax.swing.AbstractAction;
f@0 32 import javax.swing.JOptionPane;
f@0 33 import javax.swing.JTree;
f@0 34 import javax.swing.KeyStroke;
f@0 35 import javax.swing.event.TreeModelEvent;
f@0 36 import javax.swing.event.TreeModelListener;
f@0 37 import javax.swing.tree.DefaultMutableTreeNode;
f@0 38 import javax.swing.tree.TreeNode;
f@0 39 import javax.swing.tree.TreePath;
f@0 40 import javax.swing.tree.TreeSelectionModel;
f@0 41
f@0 42 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
f@0 43 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
f@0 44 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
f@0 45 import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode;
f@0 46 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode;
f@0 47 import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel;
f@0 48 import uk.ac.qmul.eecs.ccmi.diagrammodel.TypeMutableTreeNode;
f@0 49 import uk.ac.qmul.eecs.ccmi.network.Command;
f@0 50 import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
f@0 51 import uk.ac.qmul.eecs.ccmi.sound.PlayerListener;
f@0 52 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
f@0 53 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
f@0 54 import uk.ac.qmul.eecs.ccmi.speech.Narrator;
f@0 55 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
f@0 56 import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities;
f@0 57 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
f@0 58
f@0 59
f@0 60 @SuppressWarnings("serial")
f@0 61 public class DiagramTree extends JTree {
f@0 62 /**
f@0 63 * Creates a new diagram tree. The model of this tree is set to the tree model
f@0 64 * held by the instance of {@code Diagram} passed as argument. The model is retrieved by a call
f@0 65 * to {@code getTreeModel()} on the diagram.
f@0 66 * <p>
f@0 67 * The tree doesn't allow interaction via the mouse. It can be navigated via the keyboard using
f@0 68 * the arrow keys. When a node is selected, cursoring up and down allows the user to go through
f@0 69 * all the sibling of the selected node. Cursoring right will expand the selected node (if it has children)
f@0 70 * and select its first child. Cursoring left will collapse a node and select its father. All the motions
f@0 71 * trigger a text to speech utterance (possibly accompanied by sound) about the new selected node.
f@0 72 *
f@0 73 * @param diagram a reference to the diagram holding the tree model for this tree.
f@0 74 */
f@0 75 public DiagramTree(Diagram diagram){
f@0 76 super(diagram.getTreeModel());
f@0 77 this.diagram = diagram;
f@0 78 resources = ResourceBundle.getBundle(EditorFrame.class.getName());
f@0 79
f@0 80 TreePath rootPath = new TreePath((DiagramTreeNode)diagram.getTreeModel().getRoot());
f@0 81 setSelectionPath(rootPath);
f@0 82 collapsePath(rootPath);
f@0 83 selectedNodes = new ArrayList<Node>();
f@0 84 setEditable(false);
f@0 85 getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
f@0 86 overwriteTreeKeystrokes();
f@0 87 /* don't use the swing focus system as we provide one on our own */
f@0 88 setFocusTraversalKeysEnabled(false);
f@0 89 getAccessibleContext().setAccessibleName("tree");
f@0 90 }
f@0 91
f@0 92 @SuppressWarnings("unchecked")
f@0 93 @Override
f@0 94 public TreeModel<Node,Edge> getModel(){
f@0 95 return (TreeModel<Node,Edge>)super.getModel();
f@0 96 }
f@0 97
f@0 98 /**
f@0 99 * @see javax.swing.JTree#setModel(javax.swing.tree.TreeModel)
f@0 100 *
f@0 101 * @param newModel the new model for this tree
f@0 102 */
f@0 103 public void setModel(TreeModel<Node,Edge> newModel){
f@0 104 DiagramTreeNode selectedTreeNode = (DiagramTreeNode)getSelectionPath().getLastPathComponent();
f@0 105 super.setModel(newModel);
f@0 106 collapseRow(0);
f@0 107 setSelectionPath(new TreePath(selectedTreeNode.getPath()));
f@0 108 }
f@0 109
f@0 110 /**
f@0 111 * Set a new diagram for this tree. As a result of this call the tree model
f@0 112 * of this tree will be set to the model return by {@code diagram.getTreeModel()}
f@0 113 *
f@0 114 * @param diagram the new diagram for this tree
f@0 115 */
f@0 116 public void setDiagram(Diagram diagram){
f@0 117 this.diagram = diagram;
f@0 118 setModel(diagram.getTreeModel());
f@0 119 }
f@0 120
f@0 121 private void selectNode(final Node n){
f@0 122 selectedNodes.add(n);
f@0 123 treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
f@0 124
f@0 125 SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
f@0 126 @Override
f@0 127 public void playEnded() {
f@0 128 NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.node_selected"),n.spokenText()));
f@0 129 }
f@0 130 });
f@0 131 InteractionLog.log(INTERACTIONLOG_SOURCE,"node selected for edge",n.getName());
f@0 132 }
f@0 133
f@0 134 private void unselectNode(final Node n){
f@0 135 selectedNodes.remove(n);
f@0 136 treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
f@0 137
f@0 138 SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){
f@0 139 @Override
f@0 140 public void playEnded() {
f@0 141 NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.node_unselected"),n.spokenText()));
f@0 142 }
f@0 143 });
f@0 144 InteractionLog.log(INTERACTIONLOG_SOURCE,"node unselected for edge",DiagramElement.toLogString(n));
f@0 145 }
f@0 146
f@0 147 /**
f@0 148 * Returns an array containing the references to all the nodes that have so far been selected
f@0 149 * for edge creation. A new array is created each time this method is called.
f@0 150 *
f@0 151 * @return an array of nodes
f@0 152 */
f@0 153 public DiagramNode[] getSelectedNodes(){
f@0 154 DiagramNode[] array = new DiagramNode[selectedNodes.size()];
f@0 155 return selectedNodes.toArray(array);
f@0 156 }
f@0 157
f@0 158 /**
f@0 159 * Makes all the nodes selected for edge creation unselected. This method should
f@0 160 * be called after an edge has been created, to get the user restart
f@0 161 * go over the selection process again.
f@0 162 *
f@0 163 */
f@0 164 public void clearNodeSelections(){
f@0 165 ArrayList<Node> tempList = new ArrayList<Node>(selectedNodes);
f@0 166 selectedNodes.clear();
f@0 167 for(Node n : tempList){
f@0 168 treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName());
f@0 169 diagram.getModelUpdater().yieldLock(n, Lock.MUST_EXIST, new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.INSERT_EDGE,n.getId(),n.getName()));
f@0 170 }
f@0 171 }
f@0 172
f@0 173 /**
f@0 174 * Returns a string for a text to speech synthesizer, describing the currently selected
f@0 175 * tree node. The one that is at the end of the current selection path.
f@0 176 *
f@0 177 * @return a description string suitable for text to speech synthesis
f@0 178 */
f@0 179 public String currentPathSpeech(){
f@0 180 TreePath path = getSelectionPath();
f@0 181 DiagramTreeNode selectedPathTreeNode = (DiagramTreeNode)path.getLastPathComponent();
f@0 182 if(selectedNodes.contains(selectedPathTreeNode))
f@0 183 /* add information about the fact that the node is selected */
f@0 184 return MessageFormat.format(resources.getString("speech.node_selected"), selectedPathTreeNode.spokenText());
f@0 185 else
f@0 186 return selectedPathTreeNode.spokenText();
f@0 187 }
f@0 188
f@0 189 /**
f@0 190 * Changes the selected tree path from the current to one defined by
f@0 191 * the {@code JumpTo enum}
f@0 192 *
f@0 193 * @see JumpTo
f@0 194 *
f@0 195 * @param jumpTo a {@code JumpTo enum}
f@0 196 */
f@0 197 public void jump(JumpTo jumpTo){
f@0 198 final Narrator narrator = NarratorFactory.getInstance();
f@0 199 TreePath oldPath;
f@0 200 switch(jumpTo){
f@0 201 case REFERENCE :
f@0 202 oldPath = getSelectionPath();
f@0 203 DiagramTreeNode selectedTreeNode = (DiagramTreeNode)oldPath.getLastPathComponent();
f@0 204 if(selectedTreeNode instanceof NodeReferenceMutableTreeNode){
f@0 205 final Node n = (Node)((NodeReferenceMutableTreeNode)selectedTreeNode).getNode();
f@0 206 setSelectionPath(new TreePath(n.getPath()));
f@0 207 SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
f@0 208 @Override
f@0 209 public void playEnded() {
f@0 210 narrator.speak(MessageFormat.format(resources.getString("speech.jump"),n.spokenText()));
f@0 211 }
f@0 212 });
f@0 213 }else if(selectedTreeNode instanceof EdgeReferenceMutableTreeNode){
f@0 214 final Edge e = (Edge)((EdgeReferenceMutableTreeNode)selectedTreeNode).getEdge();
f@0 215 setSelectionPath(new TreePath(e.getPath()));
f@0 216 SoundFactory.getInstance().play(SoundEvent.JUMP,new PlayerListener(){
f@0 217 @Override
f@0 218 public void playEnded() {
f@0 219 narrator.speak(MessageFormat.format(resources.getString("speech.jump"),e.spokenText()));
f@0 220 }
f@0 221 });
f@0 222 }
f@0 223 /* assume the referee has only root in common with the reference and collapse everything up to the root (excluded) */
f@0 224 collapseAll(selectedTreeNode, (DiagramTreeNode)selectedTreeNode.getPath()[1]);
f@0 225 break;
f@0 226 case ROOT :
f@0 227 final DiagramTreeNode from =(DiagramTreeNode)getSelectionPath().getLastPathComponent();
f@0 228 setSelectionRow(0);
f@0 229 collapseAll(from,from.getRoot());
f@0 230 SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
f@0 231 @Override
f@0 232 public void playEnded() {
f@0 233 narrator.speak(MessageFormat.format(resources.getString("speech.jump"),from.getRoot().spokenText()));
f@0 234 }
f@0 235 });
f@0 236 break;
f@0 237 // case TYPE : // jumps to the ancestor type node of the current node, never used
f@0 238 // oldPath = getSelectionPath();
f@0 239 // int index = 0;
f@0 240 // Object[] pathComponents = oldPath.getPath();
f@0 241 // for(int i=0;i<pathComponents.length;i++){
f@0 242 // if(pathComponents[i] instanceof TypeMutableTreeNode){
f@0 243 // index=i;
f@0 244 // break;
f@0 245 // }
f@0 246 // }
f@0 247 // final DiagramTreeNode typeTreeNode = (DiagramTreeNode)oldPath.getPathComponent(index);
f@0 248 // setSelectionPath(new TreePath(typeTreeNode.getPath()));
f@0 249 // collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(),typeTreeNode);
f@0 250 // SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
f@0 251 // @Override
f@0 252 // public void playEnded() {
f@0 253 // narrator.speak(MessageFormat.format(resources.getString("speech.jump"),typeTreeNode.spokenText()));
f@0 254 // }
f@0 255 // });
f@0 256 // break;
f@0 257 case SELECTED_TYPE :
f@0 258 DiagramTreeNode root = (DiagramTreeNode)getModel().getRoot();
f@0 259 Object[] types = new Object[root.getChildCount()];
f@0 260 for(int i=0; i< root.getChildCount();i++)
f@0 261 types[i] = ((TypeMutableTreeNode)root.getChildAt(i)).getName();//not to spokenText as it would be too long
f@0 262 oldPath = getSelectionPath();
f@0 263 /* initial value is the type node whose branch node is currently selected */
f@0 264 /* it is set as the first choice in the selection dialog */
f@0 265 Object initialValue;
f@0 266 if(oldPath.getPath().length < 2)
f@0 267 initialValue = types[0];
f@0 268 else
f@0 269 initialValue = oldPath.getPathComponent(1);//type tree node
f@0 270 /* the selection from the OptionPane is the stering returned by getName() */
f@0 271 InteractionLog.log(INTERACTIONLOG_SOURCE,"open select type to jump dialog","");
f@0 272 final String selectedValue = (String)SpeechOptionPane.showSelectionDialog(
f@0 273 SpeechOptionPane.getFrameForComponent(this),
f@0 274 "select type to jump to",
f@0 275 types,
f@0 276 initialValue);
f@0 277 if(selectedValue == null){
f@0 278 /* it speaks anyway as we set up the playerListener in the EditorFrame class. No need to use narrator then */
f@0 279 SoundFactory.getInstance().play(SoundEvent.CANCEL);
f@0 280 InteractionLog.log(INTERACTIONLOG_SOURCE,"cancel select type to jump dialog","");
f@0 281 return;
f@0 282 }
f@0 283 /* we search in the root which type tree node has getName() equal to the selected one */
f@0 284 TypeMutableTreeNode typeNode = null;
f@0 285 for(int i = 0; i< root.getChildCount(); i++){
f@0 286 TypeMutableTreeNode temp = (TypeMutableTreeNode)root.getChildAt(i);
f@0 287 if(temp.getName().equals(selectedValue)){
f@0 288 typeNode = temp;
f@0 289 break;
f@0 290 }
f@0 291 }
f@0 292 setSelectionPath(new TreePath(typeNode.getPath()));
f@0 293 if(oldPath.getPath().length >= 2)
f@0 294 collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(), (DiagramTreeNode)initialValue);
f@0 295 SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
f@0 296 @Override
f@0 297 public void playEnded() {
f@0 298 narrator.speak(MessageFormat.format(resources.getString("speech.jump"),selectedValue));
f@0 299 }
f@0 300 });
f@0 301 break;
f@0 302 case BOOKMARK :
f@0 303 TreeModel<Node,Edge> treeModel = getModel();
f@0 304
f@0 305 if(treeModel.getBookmarks().size() == 0){
f@0 306 SoundFactory.getInstance().play(SoundEvent.ERROR ,new PlayerListener(){
f@0 307 @Override
f@0 308 public void playEnded() {
f@0 309 narrator.speak(resources.getString("speech.no_bookmarks"));
f@0 310 }
f@0 311 });
f@0 312 InteractionLog.log(INTERACTIONLOG_SOURCE,"no bookmarks available","");
f@0 313 return;
f@0 314 }
f@0 315
f@0 316 String[] bookmarkArray = new String[treeModel.getBookmarks().size()];
f@0 317 bookmarkArray = treeModel.getBookmarks().toArray(bookmarkArray);
f@0 318
f@0 319 InteractionLog.log(INTERACTIONLOG_SOURCE,"open select bookmark dialog","");
f@0 320 String bookmark = (String)SpeechOptionPane.showSelectionDialog(
f@0 321 JOptionPane.getFrameForComponent(this),
f@0 322 "Select bookmark",
f@0 323 bookmarkArray,
f@0 324 bookmarkArray[0]
f@0 325 );
f@0 326
f@0 327 if(bookmark != null){
f@0 328 oldPath = getSelectionPath();
f@0 329 DiagramTreeNode treeNode = treeModel.getBookmarkedTreeNode(bookmark);
f@0 330 collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(), (DiagramTreeNode)treeModel.getRoot());
f@0 331 setSelectionPath(new TreePath(treeNode.getPath()));
f@0 332 final String currentPathSpeech = currentPathSpeech();
f@0 333 SoundFactory.getInstance().play(SoundEvent.JUMP, new PlayerListener(){
f@0 334 @Override
f@0 335 public void playEnded() {
f@0 336 narrator.speak(currentPathSpeech);
f@0 337 }
f@0 338 });
f@0 339 InteractionLog.log(INTERACTIONLOG_SOURCE,"bookmark selected",bookmark);
f@0 340 }else{
f@0 341 /* it speaks anyway, as we set up the speech in the EditorFrame class. no need to use the narrator then */
f@0 342 SoundFactory.getInstance().play(SoundEvent.CANCEL);
f@0 343 InteractionLog.log(INTERACTIONLOG_SOURCE,"cancel select bookmark dialog","");
f@0 344 return;
f@0 345 }
f@0 346 break;
f@0 347
f@0 348 }
f@0 349 InteractionLog.log(INTERACTIONLOG_SOURCE,"jumped to "+jumpTo.toString(),((DiagramTreeNode)getSelectionPath().getLastPathComponent()).getName());
f@0 350 }
f@0 351
f@0 352 /**
f@0 353 * Changes the selected tree path from the current to the one from the root
f@0 354 * to the {@code Diagramelement} passed as argument. Note that a {@code Diagramelement}
f@0 355 * is also an instance of {@code DuagramTreeNode} and it's placed in a {@code TreeModel}
f@0 356 * when it's inserted into a {@code DiagramModel}
f@0 357 *
f@0 358 * @param de the diagram element to be selected on the tree
f@0 359 */
f@0 360 public void jumpTo(final DiagramElement de){
f@0 361 TreePath oldPath = getSelectionPath();
f@0 362 collapseAll((DiagramTreeNode)oldPath.getLastPathComponent(),de);
f@0 363 setSelectionPath(new TreePath(de.getPath()));
f@0 364 SoundFactory.getInstance().play( SoundEvent.JUMP, new PlayerListener(){
f@0 365 @Override
f@0 366 public void playEnded() {
f@0 367 NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.jump"),de.spokenText()));
f@0 368 }
f@0 369 });
f@0 370 }
f@0 371
f@0 372 /* collapse all the nodes in the path from "from" to "to" upwards(with the same direction as going from a leaf to the root)*/
f@0 373 private void collapseAll(DiagramTreeNode from, DiagramTreeNode to){
f@0 374 DiagramTreeNode currentNode = from;
f@0 375 while(currentNode.getParent() != null && currentNode != to){
f@0 376 currentNode = currentNode.getParent();
f@0 377 collapsePath(new TreePath(currentNode.getPath()));
f@0 378 }
f@0 379 }
f@0 380
f@0 381 /**
f@0 382 * Mouse events are ignored by this tree. This is just a blank method.
f@0 383 *
f@0 384 * @param e a mouse event
f@0 385 */
f@0 386 @Override
f@0 387 protected void processMouseEvent(MouseEvent e){
f@0 388 //do nothing as the tree does not have to be editable with mouse
f@0 389 }
f@0 390
f@0 391 /**
f@0 392 * Allows only cursor keys, tab key, delete, and actions (CTRL+something)
f@0 393 *
f@0 394 * @param e a key event
f@0 395 */
f@0 396 @Override
f@0 397 protected void processKeyEvent(KeyEvent e){
f@0 398 /* allow only cursor keys, tab key, delete, and actions (CTRL+something) */
f@0 399 if(e.getKeyChar() == KeyEvent.CHAR_UNDEFINED
f@0 400 || e.getKeyCode() == KeyEvent.VK_TAB
f@0 401 || e.getKeyCode() == KeyEvent.VK_SPACE
f@0 402 || e.isControlDown()
f@0 403 || e.isAltDown())
f@0 404 super.processKeyEvent(e);
f@0 405 }
f@0 406
f@0 407 private void overwriteTreeKeystrokes() {
f@0 408 /* overwrite the keys. up and down arrow are overwritten so that it loops when the top and the */
f@0 409 /* bottom are reached rather than getting stuck */
f@0 410
f@0 411 /* Overwrite keystrokes up,down,left,right arrows and space, shift, ctrl */
f@0 412 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,0),"down");
f@0 413 getActionMap().put("down", new AbstractAction(){
f@0 414 @Override
f@0 415 public void actionPerformed(ActionEvent evt) {
f@0 416 DiagramTreeNode treeNode = (DiagramTreeNode)getLastSelectedPathComponent();
f@0 417 /* look if we've got a sibling node after (we are not at the bottom) */
f@0 418 DiagramTreeNode nextTreeNode = treeNode.getNextSibling();
f@0 419 SoundEvent loop = null;
f@0 420 if(nextTreeNode == null){
f@0 421 DiagramTreeNode parent = treeNode.getParent();
f@0 422 if(parent == null) /* root node, just stay there */
f@0 423 nextTreeNode = treeNode;
f@0 424 else /* loop = go to first child of own parent */
f@0 425 nextTreeNode = (DiagramTreeNode)parent.getFirstChild();
f@0 426 loop = SoundEvent.LIST_BOTTOM_REACHED;
f@0 427 }
f@0 428 setSelectionPath(new TreePath(nextTreeNode.getPath()));
f@0 429 final InputStream finalSound = getTreeNodeSound(nextTreeNode);
f@0 430 final String currentPathSpeech = currentPathSpeech();
f@0 431 SoundFactory.getInstance().play(loop, new PlayerListener(){
f@0 432 public void playEnded() {
f@0 433 SoundFactory.getInstance().play(finalSound);
f@0 434 NarratorFactory.getInstance().speak(currentPathSpeech);
f@0 435 }
f@0 436 });
f@0 437 InteractionLog.log(INTERACTIONLOG_SOURCE,"move down",nextTreeNode.toString());
f@0 438 }});
f@0 439
f@0 440 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP,0),"up");
f@0 441 getActionMap().put("up", new AbstractAction(){
f@0 442 @Override
f@0 443 public void actionPerformed(ActionEvent evt) {
f@0 444 DiagramTreeNode treeNode = (DiagramTreeNode)getLastSelectedPathComponent();
f@0 445 DiagramTreeNode previousTreeNode = treeNode.getPreviousSibling();
f@0 446 SoundEvent loop = null;
f@0 447 if(previousTreeNode == null){
f@0 448 DiagramTreeNode parent = treeNode.getParent();
f@0 449 if(parent == null) /* root node */
f@0 450 previousTreeNode = treeNode;
f@0 451 else
f@0 452 previousTreeNode = (DiagramTreeNode)parent.getLastChild();
f@0 453 loop = SoundEvent.LIST_TOP_REACHED;
f@0 454 }
f@0 455 setSelectionPath(new TreePath(previousTreeNode.getPath()));
f@0 456 final InputStream finalSound = getTreeNodeSound(previousTreeNode);
f@0 457 final String currentPathSpeech = currentPathSpeech();
f@0 458 SoundFactory.getInstance().play(loop, new PlayerListener(){
f@0 459 public void playEnded() {
f@0 460 SoundFactory.getInstance().play(finalSound);
f@0 461 NarratorFactory.getInstance().speak(currentPathSpeech);
f@0 462 }
f@0 463 });
f@0 464 InteractionLog.log(INTERACTIONLOG_SOURCE,"move up",previousTreeNode.toString());
f@0 465 }});
f@0 466
f@0 467 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0),"right");
f@0 468 getActionMap().put("right", new AbstractAction(){
f@0 469 @Override
f@0 470 public void actionPerformed(ActionEvent evt) {
f@0 471 TreePath path = getSelectionPath();
f@0 472 DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();
f@0 473 if(treeNode.isLeaf()){
f@0 474 notifyBorderReached(treeNode);
f@0 475 InteractionLog.log(INTERACTIONLOG_SOURCE,"move right","border reached");
f@0 476 }
f@0 477 else{
f@0 478 expandPath(path);
f@0 479 setSelectionPath(new TreePath(((DiagramTreeNode)treeNode.getFirstChild()).getPath()));
f@0 480 final String currentPathSpeech = currentPathSpeech();
f@0 481 SoundFactory.getInstance().play(SoundEvent.TREE_NODE_EXPAND,new PlayerListener(){
f@0 482 @Override
f@0 483 public void playEnded() {
f@0 484 NarratorFactory.getInstance().speak(currentPathSpeech);
f@0 485 }
f@0 486 });
f@0 487 InteractionLog.log(INTERACTIONLOG_SOURCE,"move right",((DiagramTreeNode)treeNode.getFirstChild()).toString());
f@0 488 }
f@0 489 }
f@0 490 });
f@0 491
f@0 492 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0),"left");
f@0 493 getActionMap().put("left", new AbstractAction(){
f@0 494 @Override
f@0 495 public void actionPerformed(ActionEvent evt) {
f@0 496 TreePath path = getSelectionPath();
f@0 497 DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();
f@0 498 DiagramTreeNode parent = treeNode.getParent();
f@0 499 if(parent == null){/* root node */
f@0 500 notifyBorderReached(treeNode);
f@0 501 InteractionLog.log(INTERACTIONLOG_SOURCE,"move left","border reached");
f@0 502 }
f@0 503 else{
f@0 504 TreePath newPath = new TreePath(((DiagramTreeNode)parent).getPath());
f@0 505 setSelectionPath(newPath);
f@0 506 collapsePath(newPath);
f@0 507 final String currentPathSpeech = currentPathSpeech();
f@0 508 SoundFactory.getInstance().play(SoundEvent.TREE_NODE_COLLAPSE,new PlayerListener(){
f@0 509 @Override
f@0 510 public void playEnded() {
f@0 511 NarratorFactory.getInstance().speak(currentPathSpeech);
f@0 512 }
f@0 513 });
f@0 514 InteractionLog.log(INTERACTIONLOG_SOURCE,"move left",((DiagramTreeNode)parent).toString());
f@0 515 }
f@0 516 }
f@0 517 });
f@0 518
f@0 519 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0),"space");
f@0 520 getActionMap().put("space",new AbstractAction(){
f@0 521 @Override
f@0 522 public void actionPerformed(ActionEvent arg0) {
f@0 523 NarratorFactory.getInstance().speak(currentPathSpeech());
f@0 524 InteractionLog.log(INTERACTIONLOG_SOURCE,"info requested","");
f@0 525 }
f@0 526 });
f@0 527
f@0 528 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,InputEvent.CTRL_DOWN_MASK),"ctrlspace");
f@0 529 getActionMap().put("ctrlspace",new AbstractAction(){
f@0 530 @Override
f@0 531 public void actionPerformed(ActionEvent arg0) {
f@0 532 /*//this code snippet reads out the whole path from the root to the selected node
f@0 533 * StringBuilder builder = new StringBuilder();
f@0 534 * TreePath path = getSelectionPath();
f@0 535 * for(Object o : path.getPath()){
f@0 536 * builder.append(((DiagramModelTreeNode)o).spokenText());
f@0 537 * builder.append(", ");
f@0 538 * }
f@0 539 * Narrator.getInstance().speak(builder.toString(), null);
f@0 540 */
f@0 541 TreePath path = getSelectionPath();
f@0 542 DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();
f@0 543 NarratorFactory.getInstance().speak(treeNode.detailedSpokenText());
f@0 544 InteractionLog.log(INTERACTIONLOG_SOURCE,"detailed info requested","");
f@0 545 }
f@0 546 });
f@0 547
f@0 548 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT,InputEvent.SHIFT_DOWN_MASK),"shift");
f@0 549 getActionMap().put("shift",new AbstractAction(){
f@0 550 @Override
f@0 551 public void actionPerformed(ActionEvent evt) {
f@0 552 if(getSelectionPath().getLastPathComponent() instanceof Node){
f@0 553 Node node = (Node)getSelectionPath().getLastPathComponent();
f@0 554 if(selectedNodes.contains(node)){
f@0 555 unselectNode(node);
f@0 556 diagram.getModelUpdater().yieldLock(node, Lock.MUST_EXIST,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION,node.getId(),node.getName()));
f@0 557 }else{
f@0 558 if(!diagram.getModelUpdater().getLock(node, Lock.MUST_EXIST,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SELECT_NODE_FOR_EDGE_CREATION,node.getId(),node.getName()))){
f@0 559 InteractionLog.log(INTERACTIONLOG_SOURCE,"Could not get lock on node fro edge creation selection",DiagramElement.toLogString(node));
f@0 560 SpeechOptionPane.showMessageDialog(
f@0 561 SpeechOptionPane.getFrameForComponent(DiagramTree.this),
f@0 562 resources.getString("dialog.lock_failure.must_exist"),
f@0 563 SpeechOptionPane.INFORMATION_MESSAGE);
f@0 564 SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK, new PlayerListener(){
f@0 565 @Override
f@0 566 public void playEnded() {
f@0 567 NarratorFactory.getInstance().speak(currentPathSpeech());
f@0 568 }
f@0 569 });
f@0 570 return;
f@0 571 }
f@0 572 selectNode(node);
f@0 573 }
f@0 574 }
f@0 575 }
f@0 576 });
f@0 577
f@0 578 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown");
f@0 579 getActionMap().put("ctrldown",SpeechUtilities.getShutUpAction());
f@0 580
f@0 581 /* make the tree ignore the page up and page down keys */
f@0 582 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0),"none");
f@0 583 getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0),"none");
f@0 584 }
f@0 585
f@0 586 private static InputStream getTreeNodeSound(DiagramTreeNode node){
f@0 587 InputStream sound = null;
f@0 588 TreeNode[] newPath = node.getPath();
f@0 589 if(!node.isRoot()){
f@0 590 if(node.getChildCount() > 0){ // check whether it's the folder containing Node/Edge references
f@0 591 if(node.getChildAt(0) instanceof EdgeReferenceMutableTreeNode){
f@0 592 sound = ((EdgeReferenceMutableTreeNode)node.getChildAt(0)).getEdge().getSound();
f@0 593 }else{
f@0 594 sound = ((TypeMutableTreeNode)newPath[1]).getPrototype().getSound();
f@0 595 }
f@0 596 }else{
f@0 597 if(node instanceof NodeReferenceMutableTreeNode){
f@0 598 sound = ((NodeReferenceMutableTreeNode)node).getNode().getSound();
f@0 599 }else if(node instanceof EdgeReferenceMutableTreeNode){
f@0 600 sound = ((EdgeReferenceMutableTreeNode)node).getNode().getSound();
f@0 601 }else{
f@0 602 sound = ((TypeMutableTreeNode)newPath[1]).getPrototype().getSound();
f@0 603 }
f@0 604 }
f@0 605 }
f@0 606 return sound;
f@0 607 }
f@0 608
f@0 609 @Override
f@0 610 public void setSelectionPath(TreePath path){
f@0 611 super.setSelectionPath(path);
f@0 612 scrollPathToVisible(path);
f@0 613 }
f@0 614
f@0 615 private void notifyBorderReached(DiagramTreeNode n) {
f@0 616 SoundFactory.getInstance().play(SoundEvent.ERROR);
f@0 617 }
f@0 618
f@0 619 @Override
f@0 620 public String convertValueToText(Object value,
f@0 621 boolean selected,
f@0 622 boolean expanded,
f@0 623 boolean leaf,
f@0 624 int row,
f@0 625 boolean hasFocus){
f@0 626 StringBuilder builder = new StringBuilder(super.convertValueToText(value, selected, expanded, leaf, row, hasFocus));
f@0 627 if(selectedNodes != null)
f@0 628 if(selectedNodes.contains(value)){
f@0 629 builder.insert(0, SELECTED_NODE_MARK_BEGIN);
f@0 630 builder.append(SELECTED_NODE_MARK_END);
f@0 631 }
f@0 632 return builder.toString();
f@0 633 }
f@0 634
f@0 635 @Override
f@0 636 protected TreeModelListener createTreeModelListener(){
f@0 637 return new DiagramTreeModelHandler();
f@0 638 }
f@0 639
f@0 640
f@0 641 private List<Node> selectedNodes;
f@0 642 private Diagram diagram;
f@0 643 private ResourceBundle resources;
f@0 644 private static final char SELECTED_NODE_MARK_BEGIN = '<';
f@0 645 private static final char SELECTED_NODE_MARK_END = '>';
f@0 646 private static final String INTERACTIONLOG_SOURCE = "TREE";
f@0 647 /**
f@0 648 * A list of possible destination for a jump (a change of the selected path without
f@0 649 * using the navigation arrow keys)
f@0 650 */
f@0 651 public static enum JumpTo {
f@0 652 /**
f@0 653 * if the current selection is a edge/node reference tree node, the jump destination
f@0 654 * is the referee tree node (see {@link uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode} and
f@0 655 * {@link uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode })
f@0 656 */
f@0 657 REFERENCE,
f@0 658 /**
f@0 659 * the destination is the root of the diagram
f@0 660 */
f@0 661 ROOT,
f@0 662 /**
f@0 663 * the destination will be a node or edge type selected
f@0 664 * (via a selection dialog) by the user
f@0 665 */
f@0 666 SELECTED_TYPE,
f@0 667 /**
f@0 668 * the destination will be a bookmark selected (via a selection dialog) by the user
f@0 669 */
f@0 670 BOOKMARK}
f@0 671
f@0 672 /* the methods of the TreeModelHandler are overwritten in order to provide a consistent way
f@0 673 * of updating the tree selection upon tree change. Bear in mind that the tree can possibly be changed
f@0 674 * by another peer on a network, and therefore not only as a response to a user's action.
f@0 675 * The algorithm works as follows (being A the tree node selected before any handler method M being called):
f@0 676 *
f@0 677 * if A ain't deleted as a result of M : do nothing
f@0 678 * if A's deleted as a result of M's execution : say A was the n-th sibling select the new n-th sibling
f@0 679 * or, if now the sibling nodes are less than n, select the one with highest index
f@0 680 * if no sibling nodes are still connected to the tree select the parent or the closest ancestor connected to the tree
f@0 681 */
f@0 682 private class DiagramTreeModelHandler extends JTree.TreeModelHandler{
f@0 683
f@0 684 @Override
f@0 685 public void treeStructureChanged(final TreeModelEvent e) {
f@0 686 /* check first if what we're removing is in the selection path */
f@0 687 TreePath path = e.getTreePath();
f@0 688 boolean isInSelectionPath = false;
f@0 689 for(Object t : getSelectionPath().getPath()){
f@0 690 if(path.getLastPathComponent() == t){
f@0 691 isInSelectionPath = true;
f@0 692 break;
f@0 693 }
f@0 694 }
f@0 695
f@0 696 if(isInSelectionPath){
f@0 697 Object[] pathArray = getSelectionPath().getPath();
f@0 698 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
f@0 699 /* go along the path from the selected node to the root looking for a node *
f@0 700 * attached to the tree or with sibling nodes attached to the tree */
f@0 701 for(int i=pathArray.length-1;i>=0;i--){
f@0 702 DiagramTreeNode onPathTreeNode = (DiagramTreeNode)pathArray[i];
f@0 703 if(onPathTreeNode.isNodeRelated(root)){// if can reach the root from here a.k.a. the node is still part of the tree
f@0 704 super.treeStructureChanged(e);
f@0 705 setSelectionPath(new TreePath(onPathTreeNode.getPath()));
f@0 706 break;
f@0 707 }else{
f@0 708 /* check sibling nodes*/
f@0 709 DefaultMutableTreeNode parent = (DiagramTreeNode)pathArray[i-1];
f@0 710 if(parent.isNodeRelated(root) && parent.getChildCount() > 0){
f@0 711 super.treeStructureChanged(e);
f@0 712 setSelectionPath(new TreePath(((DefaultMutableTreeNode)parent.getLastChild()).getPath()));
f@0 713 break;
f@0 714 }
f@0 715 }
f@0 716 }
f@0 717 }else
f@0 718 super.treeStructureChanged(e);
f@0 719 repaint();
f@0 720 }
f@0 721
f@0 722 @Override
f@0 723 public void treeNodesChanged(final TreeModelEvent e){
f@0 724 TreePath path = getSelectionPath();
f@0 725 super.treeNodesChanged(e);
f@0 726 setSelectionPath(path);
f@0 727 }
f@0 728
f@0 729 @Override
f@0 730 public void treeNodesRemoved(final TreeModelEvent e){
f@0 731 /* check first if what we're removing is in the selecton path */
f@0 732 TreePath path = e.getTreePath();
f@0 733 DiagramTreeNode removedTreeNode = (DiagramTreeNode)e.getChildren()[0];
f@0 734 boolean isInSelectionPath = false;
f@0 735 for(Object t : getSelectionPath().getPath()){
f@0 736 if(removedTreeNode == t){
f@0 737 isInSelectionPath = true;
f@0 738 break;
f@0 739 }
f@0 740 }
f@0 741 DiagramTreeNode parentTreeNode = (DiagramTreeNode)path.getLastPathComponent();
f@0 742 /* update the selection only if the tree node involved is in the selection path *
f@0 743 * this always holds true for tree nodes deleted from the tree */
f@0 744 if(isInSelectionPath){
f@0 745 if(e.getSource() instanceof TreeModel){
f@0 746 /* update the path only if the node has been removed from the tree or *
f@0 747 * if the currently selected tree node is going to be removed by this action *
f@0 748 * Need to call collapsePath only if the source of the deletion is the tree *
f@0 749 * as otherwise the selected node is always a leaf */
f@0 750 collapsePath(path);
f@0 751 setSelectionPath(path);
f@0 752 }else{
f@0 753 /* if we deleted from another source, then select the first non null node in the path *
f@0 754 * including the deleted node. E.g. if we're deleting the first child of a parent *
f@0 755 * and the node has siblings than the new first sibling will be selected */
f@0 756 int limitForParentDeletion = (parentTreeNode instanceof Edge) ? 1 : 0; // an edge with one node is to be deleted
f@0 757 if(parentTreeNode.getChildCount() > limitForParentDeletion){
f@0 758 setSelectionPath(new TreePath(((DiagramTreeNode)parentTreeNode.getChildAt(
f@0 759 /* select the n-th sibling node (see algorithm description above or the highest index sibling node */
f@0 760 Math.min(e.getChildIndices()[0],parentTreeNode.getChildCount()-1)
f@0 761 )).getPath()));
f@0 762 }else{
f@0 763 /* the deleted node had no siblings, thus select the node checking from the parent up in the path to the first still existing node */
f@0 764 Object[] pathArray = path.getPath();
f@0 765 for(int i=path.getPathCount()-1;i>=0;i--){
f@0 766 DiagramTreeNode itr = (DiagramTreeNode)pathArray[i];
f@0 767 if(itr.getPath()[0] == getModel().getRoot()){
f@0 768 TreePath newPath = new TreePath(itr.getPath());
f@0 769 setSelectionPath(newPath);
f@0 770 collapsePath(newPath);
f@0 771 break;
f@0 772 }
f@0 773 }
f@0 774 }
f@0 775 }
f@0 776 }else
f@0 777 super.treeNodesRemoved(e);
f@0 778
f@0 779 /* if the node was selected for edge creation, then remove it from the list */
f@0 780 DiagramTreeNode removedNode = (DiagramTreeNode)e.getChildren()[0];
f@0 781 selectedNodes.remove(removedNode);
f@0 782 }
f@0 783 }
f@0 784 }