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