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

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