Mercurial > hg > ccmieditor
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java Fri Dec 16 17:35:51 2011 +0000 @@ -0,0 +1,723 @@ +/* + CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool + + Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +package uk.ac.qmul.eecs.ccmi.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +import javax.swing.AbstractAction; +import javax.swing.JOptionPane; +import javax.swing.JTree; +import javax.swing.KeyStroke; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; +import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode; +import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode; +import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode; +import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode; +import uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel; +import uk.ac.qmul.eecs.ccmi.diagrammodel.TypeMutableTreeNode; +import uk.ac.qmul.eecs.ccmi.sound.PlayerListener; +import uk.ac.qmul.eecs.ccmi.sound.SoundEvent; +import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; +import uk.ac.qmul.eecs.ccmi.speech.Narrator; +import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; +import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities; +import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; + +@SuppressWarnings("serial") +public class DiagramTree extends JTree { + public DiagramTree(Diagram diagram){ + super(diagram.getTreeModel()); + this.diagram = diagram; + resources = ResourceBundle.getBundle(EditorFrame.class.getName()); + + TreePath rootPath = new TreePath((DiagramModelTreeNode)diagram.getTreeModel().getRoot()); + setSelectionPath(rootPath); + collapsePath(rootPath); + selectedNodes = new ArrayList<Node>(); + setEditable(false); + getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + overwriteTreeKeystrokes(); + this.addTreeSelectionListener(new TreeSelectionListener(){ + @Override + public void valueChanged(TreeSelectionEvent evt) { + if(treeSelectionListenerGateOpen){ + final DiagramModelTreeNode treeNode = (DiagramModelTreeNode)evt.getPath().getLastPathComponent(); + if(treeNode instanceof DiagramElement){ + SoundFactory.getInstance().play(((DiagramElement)treeNode).getSound(), new PlayerListener(){ + @Override + public void playEnded() { + NarratorFactory.getInstance().speak(treeNode.spokenText()); + } + }); + }else{ + NarratorFactory.getInstance().speak(treeNode.spokenText()); + } + } + } + }); + /* don't use the swing focus system as we provide one on our own */ + setFocusTraversalKeysEnabled(false); + getAccessibleContext().setAccessibleName(""); + } + + @SuppressWarnings("unchecked") + @Override + public TreeModel<Node,Edge> getModel(){ + return (TreeModel<Node,Edge>)super.getModel(); + } + + public void setModel(TreeModel<Node,Edge> newModel){ + DiagramModelTreeNode selectedTreeNode = (DiagramModelTreeNode)getSelectionPath().getLastPathComponent(); + super.setModel(newModel); + collapseRow(0); + setSelectionPath(new TreePath(selectedTreeNode.getPath())); + } + + public void setDiagram(Diagram diagram){ + this.diagram = diagram; + setModel(diagram.getTreeModel()); + } + + public void selectNode(final Node n){ + selectedNodes.add(n); + treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName()); + + SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){ + @Override + public void playEnded() { + NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.node_selected"),n.spokenText())); + } + }); + InteractionLog.log(INTERACTIONLOG_SOURCE,"node selected for edge",n.getName()); + } + + public void unselectNode(final Node n){ + selectedNodes.remove(n); + treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName()); + + SoundFactory.getInstance().play(SoundEvent.OK,new PlayerListener(){ + @Override + public void playEnded() { + NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.node_unselected"),n.spokenText())); + } + }); + InteractionLog.log(INTERACTIONLOG_SOURCE,"node unselected for edge",DiagramElement.toLogString(n)); + } + + public DiagramNode[] getSelectedNodes(){ + DiagramNode[] array = new DiagramNode[selectedNodes.size()]; + return selectedNodes.toArray(array); + } + + public void clearNodeSelections(){ + ArrayList<Node> tempList = new ArrayList<Node>(selectedNodes); + selectedNodes.clear(); + for(Node n : tempList){ + treeModel.valueForPathChanged(new TreePath(n.getPath()),n.getName()); + diagram.getModelUpdater().yieldLock(n, Lock.MUST_EXIST); + } + } + + public String currentPathSpeech(){ + TreePath path = getSelectionPath(); + DiagramModelTreeNode selectedPathTreeNode = (DiagramModelTreeNode)path.getLastPathComponent(); + if(selectedNodes.contains(selectedPathTreeNode)) + /* add information about the fact that the node is selected */ + return MessageFormat.format(resources.getString("speech.node_selected"), selectedPathTreeNode.spokenText()); + else + return selectedPathTreeNode.spokenText(); + } + + /** + * this method changes the selected tree node from an edge/node reference + * to the related edge/node itself + */ + public void jump(JumpTo jumpTo){ + final Narrator narrator = NarratorFactory.getInstance(); + TreePath oldPath; + treeSelectionListenerGateOpen = false; + switch(jumpTo){ + case REFERENCE : + oldPath = getSelectionPath(); + DiagramModelTreeNode selectedTreeNode = (DiagramModelTreeNode)oldPath.getLastPathComponent(); + if(selectedTreeNode instanceof NodeReferenceMutableTreeNode){ + final Node n = (Node)((NodeReferenceMutableTreeNode)selectedTreeNode).getNode(); + setSelectionPath(new TreePath(n.getPath())); + SoundFactory.getInstance().setPlayerListener(new PlayerListener(){ + @Override + public void playEnded() { + narrator.speak(MessageFormat.format(resources.getString("speech.jump"),n.spokenText())); + } + }, SoundEvent.JUMP); + }else if(selectedTreeNode instanceof EdgeReferenceMutableTreeNode){ + final Edge e = (Edge)((EdgeReferenceMutableTreeNode)selectedTreeNode).getEdge(); + setSelectionPath(new TreePath(e.getPath())); + SoundFactory.getInstance().setPlayerListener(new PlayerListener(){ + @Override + public void playEnded() { + narrator.speak(MessageFormat.format(resources.getString("speech.jump"),e.spokenText())); + } + }, SoundEvent.JUMP); + } + /* assume the referee has only root in common with the reference and collapse everything up to the root (excluded) */ + collapseAll(selectedTreeNode, (DiagramModelTreeNode)selectedTreeNode.getPath()[1]); + break; + case ROOT : + final DiagramModelTreeNode from =(DiagramModelTreeNode)getSelectionPath().getLastPathComponent(); + setSelectionRow(0); + collapseAll(from,from.getRoot()); + SoundFactory.getInstance().setPlayerListener(new PlayerListener(){ + @Override + public void playEnded() { + narrator.speak(MessageFormat.format(resources.getString("speech.jump"),from.getRoot().spokenText())); + } + }, SoundEvent.JUMP); + break; + case TYPE : // jumps to the ancestor type node of the current node, never used + oldPath = getSelectionPath(); + int index = 0; + Object[] pathComponents = oldPath.getPath(); + for(int i=0;i<pathComponents.length;i++){ + if(pathComponents[i] instanceof TypeMutableTreeNode){ + index=i; + break; + } + } + final DiagramModelTreeNode typeTreeNode = (DiagramModelTreeNode)oldPath.getPathComponent(index); + setSelectionPath(new TreePath(typeTreeNode.getPath())); + collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(),typeTreeNode); + SoundFactory.getInstance().setPlayerListener(new PlayerListener(){ + @Override + public void playEnded() { + narrator.speak(MessageFormat.format(resources.getString("speech.jump"),typeTreeNode.spokenText())); + } + }, SoundEvent.JUMP); + break; + case SELECTED_TYPE : + DiagramModelTreeNode root = (DiagramModelTreeNode)getModel().getRoot(); + Object[] types = new Object[root.getChildCount()]; + for(int i=0; i< root.getChildCount();i++) + types[i] = ((TypeMutableTreeNode)root.getChildAt(i)).getName();//not to spokenText as it would be too long + oldPath = getSelectionPath(); + /* initial value is the type node whose branch node is currently selected */ + /* it is set as the first choice in the selection dialog */ + Object initialValue; + if(oldPath.getPath().length < 2) + initialValue = types[0]; + else + initialValue = oldPath.getPathComponent(1);//type tree node + /* the selection from the OptionPane is the stering returned by getName() */ + InteractionLog.log(INTERACTIONLOG_SOURCE,"open select type to jump dialog",""); + final String selectedValue = (String)SpeechOptionPane.showSelectionDialog( + SpeechOptionPane.getFrameForComponent(this), + "select type to jump to", + types, + initialValue); + if(selectedValue == null){ + /* it speaks anyway as we set up the playerListener in the EditorFrame class. No need to use narrator then */ + SoundFactory.getInstance().play(SoundEvent.CANCEL); + InteractionLog.log(INTERACTIONLOG_SOURCE,"cancel select type to jump dialog",""); + treeSelectionListenerGateOpen = true; + return; + } + /* we search in the root which type tree node has getName() equal to the selected one */ + TypeMutableTreeNode typeNode = null; + for(int i = 0; i< root.getChildCount(); i++){ + TypeMutableTreeNode temp = (TypeMutableTreeNode)root.getChildAt(i); + if(temp.getName().equals(selectedValue)){ + typeNode = temp; + break; + } + } + setSelectionPath(new TreePath(typeNode.getPath())); + if(oldPath.getPath().length >= 2) + collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(), (DiagramModelTreeNode)initialValue); + SoundFactory.getInstance().setPlayerListener(new PlayerListener(){ + @Override + public void playEnded() { + narrator.speak(MessageFormat.format(resources.getString("speech.jump"),selectedValue)); + } + }, SoundEvent.JUMP); + break; + case BOOKMARK : + TreeModel<Node,Edge> treeModel = getModel(); + + if(treeModel.getBookmarks().size() == 0){ + SoundFactory.getInstance().play(SoundEvent.ERROR ,new PlayerListener(){ + @Override + public void playEnded() { + narrator.speak("speech.no_bookmarks"); + } + }); + InteractionLog.log(INTERACTIONLOG_SOURCE,"no bookmarks available",""); + treeSelectionListenerGateOpen = true; + return; + } + + String[] bookmarkArray = new String[treeModel.getBookmarks().size()]; + bookmarkArray = treeModel.getBookmarks().toArray(bookmarkArray); + + InteractionLog.log(INTERACTIONLOG_SOURCE,"open select bookmark dialog",""); + String bookmark = (String)SpeechOptionPane.showSelectionDialog( + JOptionPane.getFrameForComponent(this), + "Select bookmark", + bookmarkArray, + bookmarkArray[0] + ); + + if(bookmark != null){ + oldPath = getSelectionPath(); + DiagramModelTreeNode treeNode = treeModel.getBookmarkedTreeNode(bookmark); + collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(), (DiagramModelTreeNode)treeModel.getRoot()); + setSelectionPath(new TreePath(treeNode.getPath())); + final String currentPathSpeech = currentPathSpeech(); + SoundFactory.getInstance().setPlayerListener(new PlayerListener(){ + @Override + public void playEnded() { + narrator.speak(currentPathSpeech); + } + }, SoundEvent.JUMP); + InteractionLog.log(INTERACTIONLOG_SOURCE,"bookmark selected",bookmark); + }else{ + /* it speaks anyway, as we set up the speech in the EditorFrame class. no need to use the narrator then */ + SoundFactory.getInstance().play(SoundEvent.CANCEL); + InteractionLog.log(INTERACTIONLOG_SOURCE,"cancel select bookmark dialog",""); + treeSelectionListenerGateOpen = true; + return; + } + break; + + } + InteractionLog.log(INTERACTIONLOG_SOURCE,"jumped to "+jumpTo.toString(),((DiagramModelTreeNode)getSelectionPath().getLastPathComponent()).getName()); + SoundFactory.getInstance().play(SoundEvent.JUMP); + treeSelectionListenerGateOpen = true; + } + + public void jumpTo(final DiagramElement de){ + TreePath oldPath = getSelectionPath(); + collapseAll((DiagramModelTreeNode)oldPath.getLastPathComponent(),de); + setSelectionPath(new TreePath(de.getPath())); + treeSelectionListenerGateOpen = false; + SoundFactory.getInstance().play( SoundEvent.JUMP, new PlayerListener(){ + @Override + public void playEnded() { + NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.jump"),de.spokenText())); + } + }); + treeSelectionListenerGateOpen = true; + } + + /* collapse all the nodes in the path from "from" to "to" upwards(with the same direction as going from a leaf to the root)*/ + private void collapseAll(DiagramModelTreeNode from, DiagramModelTreeNode to){ + DiagramModelTreeNode currentNode = from; + while(currentNode.getParent() != null && currentNode != to){ + currentNode = currentNode.getParent(); + collapsePath(new TreePath(currentNode.getPath())); + } + } + + @Override + protected void processMouseEvent(MouseEvent e){ + //do nothing as the tree does not have to be editable with mouse + } + + private void overwriteTreeKeystrokes() { + /* overwrite the keys. up and down arrow are overwritten so that it loops when the top and the */ + /* bottom are reached rather than getting stuck */ + + /* Overwrite keystrokes up,down,left,right arrows and space, shift, ctrl */ + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,0),"down"); + getActionMap().put("down", new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + DiagramModelTreeNode treeNode = (DiagramModelTreeNode)getLastSelectedPathComponent(); + /* look if we've got a sibling node after (we are not at the bottom) */ + DiagramModelTreeNode nextTreeNode = treeNode.getNextSibling(); + SoundEvent loop = null; + if(nextTreeNode == null){ + DiagramModelTreeNode parent = treeNode.getParent(); + if(parent == null) /* root node, just stay there */ + nextTreeNode = treeNode; + else /* loop = go to first child of own parent */ + nextTreeNode = (DiagramModelTreeNode)parent.getFirstChild(); + loop = SoundEvent.LIST_BOTTOM_REACHED; + } + treeSelectionListenerGateOpen = false; + setSelectionPath(new TreePath(nextTreeNode.getPath())); + final InputStream finalSound = getTreeNodeSound(nextTreeNode); + final String currentPathSpeech = currentPathSpeech(); + SoundFactory.getInstance().play(loop, new PlayerListener(){ + public void playEnded() { + SoundFactory.getInstance().play(finalSound); + NarratorFactory.getInstance().speak(currentPathSpeech); + } + }); + treeSelectionListenerGateOpen = true; + InteractionLog.log(INTERACTIONLOG_SOURCE,"move down",nextTreeNode.toString()); + }}); + + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP,0),"up"); + getActionMap().put("up", new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + DiagramModelTreeNode treeNode = (DiagramModelTreeNode)getLastSelectedPathComponent(); + DiagramModelTreeNode previousTreeNode = treeNode.getPreviousSibling(); + SoundEvent loop = null; + if(previousTreeNode == null){ + DiagramModelTreeNode parent = treeNode.getParent(); + if(parent == null) /* root node */ + previousTreeNode = treeNode; + else + previousTreeNode = (DiagramModelTreeNode)parent.getLastChild(); + loop = SoundEvent.LIST_TOP_REACHED; + } + treeSelectionListenerGateOpen = false; + setSelectionPath(new TreePath(previousTreeNode.getPath())); + final InputStream finalSound = getTreeNodeSound(previousTreeNode); + final String currentPathSpeech = currentPathSpeech(); + SoundFactory.getInstance().play(loop, new PlayerListener(){ + public void playEnded() { + SoundFactory.getInstance().play(finalSound); + NarratorFactory.getInstance().speak(currentPathSpeech); + } + }); + treeSelectionListenerGateOpen = true; + InteractionLog.log(INTERACTIONLOG_SOURCE,"move up",previousTreeNode.toString()); + }}); + + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0),"right"); + getActionMap().put("right", new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + TreePath path = getSelectionPath(); + DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent(); + if(treeNode.isLeaf()){ + notifyBorderReached(treeNode); + InteractionLog.log(INTERACTIONLOG_SOURCE,"move right","border reached"); + } + else{ + treeSelectionListenerGateOpen = false; + expandPath(path); + setSelectionPath(new TreePath(((DiagramModelTreeNode)treeNode.getFirstChild()).getPath())); + final String currentPathSpeech = currentPathSpeech(); + SoundFactory.getInstance().play(SoundEvent.TREE_NODE_EXPAND,new PlayerListener(){ + @Override + public void playEnded() { + NarratorFactory.getInstance().speak(currentPathSpeech); + } + }); + treeSelectionListenerGateOpen = true; + InteractionLog.log(INTERACTIONLOG_SOURCE,"move right",((DiagramModelTreeNode)treeNode.getFirstChild()).toString()); + } + } + }); + + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0),"left"); + getActionMap().put("left", new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + TreePath path = getSelectionPath(); + DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent(); + DiagramModelTreeNode parent = treeNode.getParent(); + if(parent == null){/* root node */ + notifyBorderReached(treeNode); + InteractionLog.log(INTERACTIONLOG_SOURCE,"move left","border reached"); + } + else{ + treeSelectionListenerGateOpen = false; + TreePath newPath = new TreePath(((DiagramModelTreeNode)parent).getPath()); + setSelectionPath(newPath); + collapsePath(newPath); + final String currentPathSpeech = currentPathSpeech(); + SoundFactory.getInstance().play(SoundEvent.TREE_NODE_COLLAPSE,new PlayerListener(){ + @Override + public void playEnded() { + NarratorFactory.getInstance().speak(currentPathSpeech); + } + }); + InteractionLog.log(INTERACTIONLOG_SOURCE,"move left",((DiagramModelTreeNode)parent).toString()); + treeSelectionListenerGateOpen = true; + } + } + }); + + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0),"space"); + getActionMap().put("space",new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent arg0) { + NarratorFactory.getInstance().speak(currentPathSpeech()); + InteractionLog.log(INTERACTIONLOG_SOURCE,"info requested",""); + } + }); + + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,InputEvent.CTRL_DOWN_MASK),"ctrlspace"); + getActionMap().put("ctrlspace",new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent arg0) { + /*//this code snippet reads out the whole path from the root to the selected node + * StringBuilder builder = new StringBuilder(); + * TreePath path = getSelectionPath(); + * for(Object o : path.getPath()){ + * builder.append(((DiagramModelTreeNode)o).spokenText()); + * builder.append(", "); + * } + * Narrator.getInstance().speak(builder.toString(), null); + */ + TreePath path = getSelectionPath(); + DiagramModelTreeNode treeNode = (DiagramModelTreeNode)path.getLastPathComponent(); + NarratorFactory.getInstance().speak(treeNode.detailedSpokenText()); + InteractionLog.log(INTERACTIONLOG_SOURCE,"detailed info requested",""); + } + }); + + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT,InputEvent.SHIFT_DOWN_MASK),"shift"); + getActionMap().put("shift",new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + if(getSelectionPath().getLastPathComponent() instanceof Node){ + Node node = (Node)getSelectionPath().getLastPathComponent(); + + + if(selectedNodes.contains(node)){ + unselectNode(node); + diagram.getModelUpdater().yieldLock(node, Lock.MUST_EXIST); + } + else{ + if(!diagram.getModelUpdater().getLock(node, Lock.MUST_EXIST)){ + InteractionLog.log(INTERACTIONLOG_SOURCE,"Could not get lock on node fro edge creation selection",DiagramElement.toLogString(node)); + SpeechOptionPane.showMessageDialog( + SpeechOptionPane.getFrameForComponent(DiagramTree.this), + resources.getString("dialog.lock_failure.must_exist"), + SpeechOptionPane.INFORMATION_MESSAGE); + SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK, new PlayerListener(){ + @Override + public void playEnded() { + NarratorFactory.getInstance().speak(currentPathSpeech()); + } + }); + return; + } + selectNode(node); + } + } + } + }); + + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown"); + getActionMap().put("ctrldown",SpeechUtilities.getShutUpAction()); + + /* make the tree ignore the page up and page down keys */ + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0),"none"); + getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0),"none"); + } + + private static InputStream getTreeNodeSound(DiagramModelTreeNode node){ + InputStream sound = null; + TreeNode[] newPath = node.getPath(); + if(!node.isRoot()){ + if(node.getChildCount() > 0){ // check whether it's the folder containing Node/Edge references + if(node.getChildAt(0) instanceof EdgeReferenceMutableTreeNode){ + sound = ((EdgeReferenceMutableTreeNode)node.getChildAt(0)).getEdge().getSound(); + }else{ + sound = ((TypeMutableTreeNode)newPath[1]).getPrototype().getSound(); + } + }else{ + if(node instanceof NodeReferenceMutableTreeNode){ + sound = ((NodeReferenceMutableTreeNode)node).getNode().getSound(); + }else if(node instanceof EdgeReferenceMutableTreeNode){ + sound = ((EdgeReferenceMutableTreeNode)node).getNode().getSound(); + }else{ + sound = ((TypeMutableTreeNode)newPath[1]).getPrototype().getSound(); + } + } + } + return sound; + } + + @Override + public void setSelectionPath(TreePath path){ + super.setSelectionPath(path); + scrollPathToVisible(path); + } + + private void notifyBorderReached(DiagramModelTreeNode n) { + SoundFactory.getInstance().play(SoundEvent.ERROR); + } + + @Override + public String convertValueToText(Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus){ + StringBuilder builder = new StringBuilder(super.convertValueToText(value, selected, expanded, leaf, row, hasFocus)); + if(selectedNodes != null) + if(selectedNodes.contains(value)){ + builder.insert(0, SELECTED_NODE_MARK_BEGIN); + builder.append(SELECTED_NODE_MARK_END); + } + return builder.toString(); + } + + @Override + protected TreeModelListener createTreeModelListener(){ + return new DiagramTreeModelHandler(); + } + + + private List<Node> selectedNodes; + private Diagram diagram; + private ResourceBundle resources; + private boolean treeSelectionListenerGateOpen; + private static final char SELECTED_NODE_MARK_BEGIN = '<'; + private static final char SELECTED_NODE_MARK_END = '>'; + private static final String INTERACTIONLOG_SOURCE = "TREE"; + static enum JumpTo {REFERENCE, ROOT, TYPE, SELECTED_TYPE, BOOKMARK} + + /* the methods of the TreeModelHandler are overwritten in order to provide a consistent way + * of updating the tree selection upon tree change. Bear in mind that the tree can possibly be changed + * by another peer on a network, and therefore not only as a response to a user's action. + * The algorithm works as follows (being A the tree node selected before any handler method M being called): + * + * if A ain't deleted as a result of M : do nothing + * if A's deleted as a result of M's execution : say A was the n-th sibling select the new n-th sibling + * or, if now the sibling nodes are less than n, select the one with highest index + * if no sibling nodes are still connected to the tree select the parent or the closest ancestor connected to the tree + */ + private class DiagramTreeModelHandler extends JTree.TreeModelHandler{ + + @Override + public void treeStructureChanged(final TreeModelEvent e) { + /* check first if what we're removing is in the selection path */ + TreePath path = e.getTreePath(); + boolean isInSelectionPath = false; + for(Object t : getSelectionPath().getPath()){ + if(path.getLastPathComponent() == t){ + isInSelectionPath = true; + break; + } + } + + if(isInSelectionPath){ + Object[] pathArray = getSelectionPath().getPath(); + DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot(); + /* go along the path from the selected node to the root looking for a node * + * attached to the tree or with sibling nodes attached to the tree */ + for(int i=pathArray.length-1;i>=0;i--){ + DiagramModelTreeNode onPathTreeNode = (DiagramModelTreeNode)pathArray[i]; + if(onPathTreeNode.isNodeRelated(root)){// if can reach the root from here a.k.a. the node is still part of the tree + super.treeStructureChanged(e); + setSelectionPath(new TreePath(onPathTreeNode.getPath())); + break; + }else{ + /* check sibling nodes*/ + DefaultMutableTreeNode parent = (DiagramModelTreeNode)pathArray[i-1]; + if(parent.isNodeRelated(root) && parent.getChildCount() > 0){ + super.treeStructureChanged(e); + setSelectionPath(new TreePath(((DefaultMutableTreeNode)parent.getLastChild()).getPath())); + break; + } + } + } + }else + super.treeStructureChanged(e); + repaint(); + } + + @Override + public void treeNodesChanged(final TreeModelEvent e){ + TreePath path = getSelectionPath(); + super.treeNodesChanged(e); + setSelectionPath(path); + } + + @Override + public void treeNodesRemoved(final TreeModelEvent e){ + /* check first if what we're removing is in the selecton path */ + TreePath path = e.getTreePath(); + DiagramModelTreeNode removedTreeNode = (DiagramModelTreeNode)e.getChildren()[0]; + boolean isInSelectionPath = false; + for(Object t : getSelectionPath().getPath()){ + if(removedTreeNode == t){ + isInSelectionPath = true; + break; + } + } + DiagramModelTreeNode parentTreeNode = (DiagramModelTreeNode)path.getLastPathComponent(); + /* update the selection only if the tree node involved is in the selection path * + * this always holds true for tree nodes deleted from the tree */ + if(isInSelectionPath){ + if(e.getSource() instanceof TreeModel){ + /* update the path only if the node has been removed from the tree or * + * if the currently selected tree node is going to be removed by this action * + * Need to call collapsePath only if the source of the deletion is the tree * + * as otherwise the selected node is always a leaf */ + collapsePath(path); + setSelectionPath(path); + }else{ + /* if we deleted from another source, then select the first non null node in the path * + * including the deleted node. E.g. if we're deleting the first child of a parent * + * and the node has siblings than the new first sibling will be selected */ + int limitForParentDeletion = (parentTreeNode instanceof Edge) ? 1 : 0; // an edge with one node is to be deleted + if(parentTreeNode.getChildCount() > limitForParentDeletion){ + setSelectionPath(new TreePath(((DiagramModelTreeNode)parentTreeNode.getChildAt( + /* select the n-th sibling node (see algorithm description above or the highest index sibling node */ + Math.min(e.getChildIndices()[0],parentTreeNode.getChildCount()-1) + )).getPath())); + }else{ + /* the deleted node had no siblings, thus select the node checking from the parent up in the path to the first still existing node */ + Object[] pathArray = path.getPath(); + for(int i=path.getPathCount()-1;i>=0;i--){ + DiagramModelTreeNode itr = (DiagramModelTreeNode)pathArray[i]; + if(itr.getPath()[0] == getModel().getRoot()){ + TreePath newPath = new TreePath(itr.getPath()); + setSelectionPath(newPath); + collapsePath(newPath); + break; + } + } + } + } + }else + super.treeNodesRemoved(e); + + /* if the node was selected for edge creation, then remove it from the list */ + DiagramModelTreeNode removedNode = (DiagramModelTreeNode)e.getChildren()[0]; + selectedNodes.remove(removedNode); + } + } +}