annotate java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java @ 8:ea7885bd9bff tip

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