annotate java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java @ 2:4b2f975e35fa

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