1 33 34 package edu.rice.cs.util.docnavigation; 35 36 import javax.swing.*; 37 import javax.swing.event.TreeSelectionListener ; 38 import javax.swing.event.TreeSelectionEvent ; 39 import javax.swing.event.TreeExpansionListener ; 40 import javax.swing.event.TreeExpansionEvent ; 41 import javax.swing.tree.*; 42 import java.io.File ; 43 import java.awt.*; 44 import java.util.*; 45 import edu.rice.cs.util.*; 46 import edu.rice.cs.util.swing.*; 47 48 public class JTreeSortNavigator<ItemT extends INavigatorItem> extends JTree 49 implements IDocumentNavigator<ItemT>, TreeSelectionListener , TreeExpansionListener { 50 51 52 private final DefaultTreeModel _model; 53 54 57 private volatile NodeData<ItemT> _current; 58 59 60 private final HashMap<ItemT, LeafNode<ItemT>> _doc2node = new HashMap<ItemT, LeafNode<ItemT>>(); 61 62 63 private final BidirectionalHashMap<String , InnerNode<?, ItemT>> _path2node = new BidirectionalHashMap<String , InnerNode<?, ItemT>>(); 64 65 70 73 74 private final Vector<INavigationListener<? super ItemT>> navListeners = new Vector<INavigationListener<? super ItemT>>(); 75 76 77 private final CustomTreeCellRenderer _renderer; 78 79 private volatile DisplayManager<? super ItemT> _displayManager; 80 private volatile Icon _rootIcon; 81 82 private java.util.List <GroupNode<ItemT>> _roots = new LinkedList<GroupNode<ItemT>>(); 83 84 87 public void setForeground(Color c) { 88 super.setForeground(c); 89 if (_renderer != null) _renderer.setTextNonSelectionColor(c); 90 } 91 92 94 public void setBackground(Color c) { 95 super.setBackground(c); 96 if (_renderer != null) _renderer.setBackgroundNonSelectionColor(c); 97 } 98 99 102 public JTreeSortNavigator(String projRoot) { 103 104 super(new DefaultTreeModel(new RootNode<ItemT>(projRoot.substring(projRoot.lastIndexOf(File.separator) + 1)))); 105 106 addTreeSelectionListener(this); 107 addTreeExpansionListener(this); 108 109 _model = (DefaultTreeModel) getModel(); 110 _renderer = new CustomTreeCellRenderer(); 111 _renderer.setOpaque(false); 112 setCellRenderer(_renderer); 113 getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 114 setRowHeight(18); 115 } 117 118 122 public JTreeSortNavigator(String projRoot, DisplayManager<? super ItemT> dm) { 123 this(projRoot); 124 _displayManager = dm; 125 } 126 127 130 public void setDisplayManager(DisplayManager<? super ItemT> manager) { _displayManager = manager; } 131 132 133 public void setRootIcon(Icon ico) { _rootIcon = ico; } 134 135 136 public Container asContainer() { return this; } 137 138 141 public void addDocument(ItemT doc) { 142 assert EventQueue.isDispatchThread(); 143 addDocument(doc, ""); 144 } 162 170 public void addDocument(ItemT doc, String path) { 171 assert EventQueue.isDispatchThread(); 172 synchronized(_model) { 174 175 GroupNode<ItemT> root = null; 176 177 for (GroupNode<ItemT> r: _roots) { 178 if (r.getFilter().accept(doc)) { 179 root = r; 180 break; 181 } 182 } 183 184 if (root == null) return; 185 186 187 StringTokenizer tok = new StringTokenizer(path, File.separator); 188 final StringBuilder pathSoFarBuf = new StringBuilder (); 190 InnerNode<?, ItemT> lastNode = root; 191 while (tok.hasMoreTokens()) { 192 String element = tok.nextToken(); 193 pathSoFarBuf.append(element).append('/'); 194 String pathSoFar = pathSoFarBuf.toString(); 195 InnerNode<?, ItemT> thisNode; 196 if (!_path2node.containsKey(pathSoFar)) { 199 201 202 thisNode = new FileNode<ItemT>(new File (pathSoFar)); 203 insertFolderSortedInto(thisNode, lastNode); 204 this.expandPath(new TreePath(lastNode.getPath())); 205 _path2node.put(pathSoFar, thisNode); 207 } 208 else { 209 thisNode = _path2node.getValue(pathSoFar); 211 } 212 213 lastNode = thisNode; 214 215 } 217 218 219 220 LeafNode<ItemT> child = new LeafNode<ItemT>(doc); 221 _doc2node.put(doc, child); 222 insertNodeSortedInto(child, lastNode); 223 this.expandPath(new TreePath(lastNode.getPath())); 226 } 227 } 228 229 private void addTopLevelGroupToRoot(InnerNode<?, ItemT> parent) { 230 assert EventQueue.isDispatchThread(); 231 synchronized(_model) { int indexInRoots = _roots.indexOf(parent); 233 int num = _model.getChildCount(_model.getRoot()); 234 int i; 235 for (i = 0; i < num; i++) { 236 TreeNode n = (TreeNode)_model.getChild(_model.getRoot(), i); 237 if(_roots.indexOf(n) > indexInRoots) break; 238 } 239 _model.insertNodeInto(parent, (MutableTreeNode)_model.getRoot(), i); 240 } 241 } 242 243 248 private void insertNodeSortedInto(LeafNode<ItemT> child, InnerNode<?, ItemT> parent) { 249 int numChildren = parent.getChildCount(); 250 String newName = child.toString(); 251 String oldName = parent.getUserObject().toString(); 252 DefaultMutableTreeNode parentsKid; 253 254 255 if (((DefaultMutableTreeNode)_model.getRoot()).getIndex(parent) == -1 && _roots.contains(parent)) { 256 addTopLevelGroupToRoot(parent); 257 } 258 int i; 259 for (i = 0; i < numChildren; i++ ) { 260 parentsKid = ((DefaultMutableTreeNode) parent.getChildAt(i)); 261 if (parentsKid instanceof InnerNode) { 262 } else if(parentsKid instanceof LeafNode) { 264 oldName = ((LeafNode<?>)parentsKid).getData().getName(); 265 if ((newName.toUpperCase().compareTo(oldName.toUpperCase()) < 0)) break; 266 } else throw new IllegalStateException ("found a node in navigator that is not an InnerNode or LeafNode"); 267 } 268 _model.insertNodeInto(child, parent, i); 269 } 270 271 276 private void insertFolderSortedInto(InnerNode<?, ItemT> child, InnerNode<?, ItemT> parent) { 277 int numChildren = parent.getChildCount(); 278 String newName = child.toString(); 279 String oldName = parent.getUserObject().toString(); 280 DefaultMutableTreeNode parentsKid; 281 282 if (((DefaultMutableTreeNode)_model.getRoot()).getIndex(parent) == -1 && _roots.contains(parent)) { 283 addTopLevelGroupToRoot(parent); 284 } 285 286 int countFolders = 0; 287 int i; 288 for (i = 0; i < numChildren; i++) { 289 parentsKid = ((DefaultMutableTreeNode)parent.getChildAt(i)); 290 if (parentsKid instanceof InnerNode) { 291 countFolders++; 292 oldName = parentsKid.toString(); 293 if ((newName.toUpperCase().compareTo(oldName.toUpperCase()) < 0)) break; 294 } 295 else if (parentsKid instanceof LeafNode) break; 296 else throw new IllegalStateException ("found a node in navigator that is not an InnerNode or LeafNode"); 298 } 299 _model.insertNodeInto(child, parent, i); 300 } 301 302 310 public ItemT removeDocument(ItemT doc) { 311 assert EventQueue.isDispatchThread(); 312 synchronized(_model) { LeafNode<ItemT> toRemove = getNodeForDoc(doc); 314 if (toRemove == null) return null; 315 return removeNode(getNodeForDoc(doc)); 316 } 317 } 318 319 320 private LeafNode<ItemT> getNodeForDoc(ItemT doc) { 321 return _doc2node.get(doc); 323 } 325 326 328 private ItemT removeNode(LeafNode<ItemT> node) { 329 DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); 330 _model.removeNodeFromParent(node); 331 _doc2node.remove(node.getData()); 332 333 cleanFolderNode(parent); 334 347 348 return node.getData(); 350 } 351 352 355 private void cleanFolderNode(DefaultMutableTreeNode node) { 356 if (node instanceof InnerNode && node.getChildCount() == 0) { 358 DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); 359 _model.removeNodeFromParent(node); 360 @SuppressWarnings ("unchecked") InnerNode<?, ItemT> typedNode = (InnerNode<?, ItemT>) node; 361 _path2node.removeKey(typedNode); 362 cleanFolderNode(parent); 363 } 364 } 366 367 376 public void refreshDocument(ItemT doc, String path) { 377 assert EventQueue.isDispatchThread(); 378 LeafNode<ItemT> node = _doc2node.get(doc); 380 InnerNode<?, ?> oldParent; 381 if (node == null) { addDocument(doc, path); 383 return; } 386 InnerNode<?, ?> p = (InnerNode<?, ?>) node.getParent(); 388 oldParent = p; 389 391 String newPath = path; 393 394 if (newPath.length() > 0) { 395 if (newPath.substring(0,1).equals("/")) newPath = newPath.substring(1); 396 if (! newPath.substring(newPath.length() - 1).equals("/")) newPath = newPath + "/"; 397 } 398 399 InnerNode<?, ItemT> newParent = _path2node.getValue(newPath); 401 407 if (newParent == oldParent) { if (! node.toString().equals(doc.getName())) { synchronized(_model) { 410 LeafNode<ItemT> newLeaf= new LeafNode<ItemT>(doc); 411 _doc2node.put(doc, newLeaf); 412 insertNodeSortedInto(newLeaf, newParent); 413 _model.removeNodeFromParent(node); 414 } 415 } 416 } 418 else { synchronized(_model) { 420 removeNode(node); 421 addDocument(doc, path); 422 } 423 } 424 } 426 427 428 public void setActiveDoc(ItemT doc) { 429 assert EventQueue.isDispatchThread(); 430 DefaultMutableTreeNode node = _doc2node.get(doc); 432 if (node == null) return; if (node == _current) return; TreeNode[] nodes = node.getPath(); 436 TreePath path = new TreePath(nodes); 437 expandPath(path); 438 setSelectionPath(path); scrollPathToVisible(path); 440 } 443 444 449 private ItemT getNodeUserObject(DefaultMutableTreeNode n) { 450 @SuppressWarnings ("unchecked") ItemT result = (ItemT) n.getUserObject(); 451 return result; 452 } 453 454 458 public ItemT getNext(ItemT doc) { 459 synchronized(_model) { DefaultMutableTreeNode node = _doc2node.get(doc); 461 if (node == null) return doc; DefaultMutableTreeNode next = node.getNextLeaf(); 464 if (next == null || next == _model.getRoot()) { return doc; } 465 else { return getNodeUserObject(next); } 466 } 467 } 468 469 473 public ItemT getPrevious(ItemT doc) { 474 synchronized(_model) { DefaultMutableTreeNode node = _doc2node.get(doc); 476 if (node == null) return doc; DefaultMutableTreeNode prev = node.getPreviousLeaf(); 479 if (prev == null || prev == _model.getRoot()) { return doc; } 480 else { return getNodeUserObject(prev); } 481 } 482 } 483 484 487 public ItemT getFirst() { 488 synchronized(_model) { DefaultMutableTreeNode root = (DefaultMutableTreeNode) _model.getRoot(); 490 return getNodeUserObject(root.getFirstLeaf()); 491 } 492 } 493 494 497 public ItemT getLast() { 498 synchronized(_model) { DefaultMutableTreeNode root = (DefaultMutableTreeNode) _model.getRoot(); 500 return getNodeUserObject(root.getLastLeaf()); 501 } 502 } 503 504 509 public boolean contains(ItemT doc) { 510 synchronized(_model) { return _doc2node.containsKey(doc); } } 512 513 514 public boolean _contains(ItemT doc) { return _doc2node.containsKey(doc); } 515 516 520 public Enumeration<ItemT> getDocuments() { 521 522 final Vector<ItemT> list = new Vector<ItemT>(getDocumentCount()); 524 synchronized(_model) { Enumeration e = ((DefaultMutableTreeNode)_model.getRoot()).depthFirstEnumeration(); 527 528 while(e.hasMoreElements()) { 529 DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement(); 530 if (node.isLeaf() && node != _model.getRoot()) { 531 list.add(getNodeUserObject(node)); 532 } 533 } 534 } 535 return list.elements(); 536 } 537 538 544 public int getDocumentCount() { return _doc2node.size(); } 545 546 550 public boolean isEmpty() { return _doc2node.isEmpty(); } 551 552 553 public void clear() { 554 assert EventQueue.isDispatchThread(); 555 synchronized(_model) { 556 _doc2node.clear(); 557 ((DefaultMutableTreeNode)_model.getRoot()).removeAllChildren(); 558 } 559 } 560 561 566 public void addNavigationListener(INavigationListener<? super ItemT> listener) { 567 assert EventQueue.isDispatchThread(); 568 synchronized(_model) { navListeners.add(listener); } } 570 571 576 public void removeNavigationListener(INavigationListener<? super ItemT> listener) { 577 assert EventQueue.isDispatchThread(); 578 synchronized(_model) { navListeners.remove(listener); } 579 } 580 581 584 public Collection<INavigationListener<? super ItemT>> getNavigatorListeners() { return navListeners; } 585 586 590 public <InType, ReturnType> ReturnType execute(IDocumentNavigatorAlgo<ItemT, InType, ReturnType> algo, InType input) { 591 return algo.forTree(this, input); 592 } 593 594 597 public void valueChanged(TreeSelectionEvent e) { 598 Object treeNode = this.getLastSelectedPathComponent(); 600 if (treeNode == null || !(treeNode instanceof NodeData)) return; 601 @SuppressWarnings ("unchecked") NodeData<ItemT> newSelection = (NodeData<ItemT>) treeNode; 602 if (_current != newSelection) { 603 for(INavigationListener<? super ItemT> listener : navListeners) { 604 listener.lostSelection(_current, isNextChangeModelInitiated()); 605 listener.gainedSelection(newSelection, isNextChangeModelInitiated()); 606 } 607 _current = newSelection; 608 } 609 610 setNextChangeModelInitiated(false); 611 } 613 614 615 public Component getRenderer() { return _renderer; } 616 617 618 private class CustomTreeCellRenderer extends DefaultTreeCellRenderer { 619 620 623 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean isExpanded, 624 boolean leaf, int row, boolean hasFocus) { 625 626 super.getTreeCellRendererComponent(tree, value, sel, isExpanded, leaf, row, hasFocus); 627 628 DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; 629 if (node instanceof RootNode && _rootIcon != null) setIcon(_rootIcon); 630 631 else if (node instanceof LeafNode) { 632 ItemT doc = getNodeUserObject(node); 633 if (leaf && _displayManager != null) { 634 setIcon(_displayManager.getIcon(doc)); 635 setText(_displayManager.getName(doc)); 636 } 637 } 638 return this; 639 } 640 } 641 642 643 648 public boolean selectDocumentAt(int x, int y) { 649 TreePath path = getPathForLocation(x, y); 651 if (path == null) return false; 652 else { 653 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); 654 if (node instanceof LeafNode) { 655 this.expandPath(path); 656 this.setSelectionPath(path); 657 this.scrollPathToVisible(path); 658 return true; 659 } 660 else if (node instanceof InnerNode) { 661 this.expandPath(path); 662 this.setSelectionPath(path); 663 this.scrollPathToVisible(path); 664 return true; 665 } 666 else if (node instanceof RootNode) { 667 this.expandPath(path); 668 this.setSelectionPath(path); 669 this.scrollPathToVisible(path); 670 return true; 671 } 672 else return false; 673 } 674 } 676 677 678 public boolean isGroupSelected() { 679 TreePath p = getSelectionPath(); 681 TreeNode n = (TreeNode) p.getLastPathComponent(); 682 return (n instanceof InnerNode); 683 } 685 686 687 public boolean isTopLevelGroupSelected() { 688 TreePath p = getSelectionPath(); 690 TreeNode n = (TreeNode) p.getLastPathComponent(); 691 return (n instanceof GroupNode); 692 } 694 695 696 public String getNameOfSelectedTopLevelGroup() throws GroupNotSelectedException { 697 assert EventQueue.isDispatchThread(); 698 699 TreePath p = getSelectionPath(); 700 TreeNode n = (TreeNode) p.getLastPathComponent(); 701 702 if (n == _model.getRoot()) 703 throw new GroupNotSelectedException("there is no top level group for the root of the tree"); 704 705 while (! _roots.contains(n)) { n = n.getParent(); } 706 707 return ((GroupNode<?>)n).getData(); 708 } 709 710 712 public ItemT getCurrent() { 713 NodeData<ItemT> current = _current; 714 if (current == null) return null; 715 return current.execute(_leafVisitor); 716 } 717 718 719 public Object getModelLock() { return _model; } 720 721 private final NodeDataVisitor<ItemT, ItemT> _leafVisitor = new NodeDataVisitor<ItemT, ItemT>() { 722 public ItemT fileCase(File f, Object ... p){ return null; } 723 public ItemT stringCase(String s, Object ... p){ return null; } 724 public ItemT itemCase(ItemT ini, Object ... p){ return ini; } 725 }; 726 727 728 public boolean isSelectedInGroup(ItemT i) { 729 TreePath p = getSelectionPath(); 731 TreeNode n = (TreeNode) p.getLastPathComponent(); 732 TreeNode l = _doc2node.get(i); 733 734 if (n == _model.getRoot()) return true; 735 736 while (l.getParent() != _model.getRoot()) { 737 if(l.getParent() == n) return true; 738 l = l.getParent(); 739 } 740 741 return false; 742 } 744 745 746 public void addTopLevelGroup(String name, INavigatorItemFilter<? super ItemT> f){ 747 if (f == null) 748 throw new IllegalArgumentException ("parameter 'f' is not allowed to be null"); 749 GroupNode<ItemT> n = new GroupNode<ItemT>(name, f); 750 _roots.add(n); 751 } 752 753 754 755 756 public void treeCollapsed(TreeExpansionEvent event) { 757 Object o = event.getPath().getLastPathComponent(); 758 if (o instanceof InnerNode) ((InnerNode<?, ?>)o).setCollapsed(true); 759 } 760 761 762 public void treeExpanded(TreeExpansionEvent event) { 763 Object o = event.getPath().getLastPathComponent(); 764 if (o instanceof InnerNode) ((InnerNode<?, ?>)o).setCollapsed(false); 765 } 766 767 772 public void collapsePaths(String [] paths) { 773 assert EventQueue.isDispatchThread(); 774 775 HashSet<String > set = new HashSet<String >(); 776 for (String s : paths) { set.add(s); } 777 collapsePaths(set); 778 } 779 780 783 void collapsePaths(HashSet<String > paths) { 784 785 DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)_model.getRoot(); 786 Enumeration nodes = rootNode.depthFirstEnumeration(); 788 ArrayList<String > list = new ArrayList<String >(); 789 while (nodes.hasMoreElements()) { 790 DefaultMutableTreeNode tn = (DefaultMutableTreeNode)nodes.nextElement(); 791 if (tn instanceof InnerNode) { 792 TreePath tp = new TreePath(tn.getPath()); 793 String s = generatePathString(tp); 794 boolean shouldCollapse = paths.contains(s); 795 if (shouldCollapse) { 796 collapsePath(tp); 797 } 798 } 799 } 800 } 801 802 806 public String [] getCollapsedPaths() { 807 ArrayList<String > list = new ArrayList<String >(); 808 DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)_model.getRoot(); 810 Enumeration nodes = rootNode.depthFirstEnumeration(); 812 while (nodes.hasMoreElements()) { 813 DefaultMutableTreeNode tn = (DefaultMutableTreeNode)nodes.nextElement(); 814 if (tn instanceof InnerNode && ((InnerNode<?, ?>)tn).isCollapsed()) { 815 TreePath tp = new TreePath(tn.getPath()); 816 list.add(generatePathString(tp)); 817 } 818 } 819 return list.toArray(new String [list.size()]); 821 } 822 823 830 public String generatePathString(TreePath tp) { 831 String path = ""; 832 TreeNode root = (TreeNode) _model.getRoot(); 834 835 while (tp != null) { 836 TreeNode curr = (TreeNode) tp.getLastPathComponent(); 837 if (curr == root) path = "./" + path; 838 else path = curr + "/" + path; 839 tp = tp.getParentPath(); 840 } 842 843 return path; 844 } 845 846 847 public void requestSelectionUpdate(ItemT ini) { 848 if (getCurrent() == null) { setActiveDoc(ini); 851 } 852 } 854 855 856 public void setNextChangeModelInitiated(boolean b) { 857 putClientProperty(MODEL_INITIATED_PROPERTY_NAME, b?Boolean.TRUE:null); 858 } 859 860 861 public boolean isNextChangeModelInitiated() { 862 return getClientProperty(MODEL_INITIATED_PROPERTY_NAME) != null; 863 } 864 865 } 868 869 | Popular Tags |