KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > rice > cs > util > docnavigation > JTreeSortNavigator


1  /*BEGIN_COPYRIGHT_BLOCK
2  *
3  * This file is part of DrJava. Download the current version of this project from http://www.drjava.org/
4  * or http://sourceforge.net/projects/drjava/
5  *
6  * DrJava Open Source License
7  *
8  * Copyright (C) 2001-2006 JavaPLT group at Rice University (javaplt@rice.edu). All rights reserved.
9  *
10  * Developed by: Java Programming Languages Team, Rice University, http://www.cs.rice.edu/~javaplt/
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
13  * documentation files (the "Software"), to deal with the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
15  * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
16  *
17  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the
18  * following disclaimers.
19  * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
20  * following disclaimers in the documentation and/or other materials provided with the distribution.
21  * - Neither the names of DrJava, the JavaPLT, Rice University, nor the names of its contributors may be used to
22  * endorse or promote products derived from this Software without specific prior written permission.
23  * - Products derived from this software may not be called "DrJava" nor use the term "DrJava" as part of their
24  * names without prior written permission from the JavaPLT group. For permission, write to javaplt@rice.edu.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
27  * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28  * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
29  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
30  * WITH THE SOFTWARE.
31  *
32  *END_COPYRIGHT_BLOCK*/

33
34 package edu.rice.cs.util.docnavigation;
35
36 import javax.swing.*;
37 import javax.swing.event.TreeSelectionListener JavaDoc;
38 import javax.swing.event.TreeSelectionEvent JavaDoc;
39 import javax.swing.event.TreeExpansionListener JavaDoc;
40 import javax.swing.event.TreeExpansionEvent JavaDoc;
41 import javax.swing.tree.*;
42 import java.io.File JavaDoc;
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 JavaDoc, TreeExpansionListener JavaDoc {
50   
51   /** The model of the tree. */
52   private final DefaultTreeModel _model;
53    
54   /** The currently selected item. Updated by a listener. It is not volatile because all accessed are protected by
55    * explicit synchronization.
56    */

57   private volatile NodeData<ItemT> _current;
58   
59   /** Maps documents to tree nodes. */
60   private final HashMap<ItemT, LeafNode<ItemT>> _doc2node = new HashMap<ItemT, LeafNode<ItemT>>();
61   
62   /** Maps path's to nodes and nodes to paths. */
63   private final BidirectionalHashMap<String JavaDoc, InnerNode<?, ItemT>> _path2node = new BidirectionalHashMap<String JavaDoc, InnerNode<?, ItemT>>();
64   
65 // /** The node corresponding to the [external files] node in the tree this will hold files that are not in
66
// * the project directory
67
// */
68
// private StringNode<ItemT> _nonProjRoot = new StringNode<ItemT>("[External Files]");
69

70 // /** Flag indicating if the nonproject node has children. */
71
// private boolean _hasNonProjFilesOpen = false;
72

73   /** The collection of INavigationListeners listening to this JListNavigator */
74   private final Vector<INavigationListener<? super ItemT>> navListeners = new Vector<INavigationListener<? super ItemT>>();
75   
76   /** The renderer for this JTree. */
77   private final CustomTreeCellRenderer _renderer;
78   
79   private volatile DisplayManager<? super ItemT> _displayManager;
80   private volatile Icon _rootIcon;
81   
82   private java.util.List JavaDoc<GroupNode<ItemT>> _roots = new LinkedList<GroupNode<ItemT>>();
83   
84   /** Sets the foreground color of this JTree
85    * @param c the color to set to
86    */

87   public void setForeground(Color c) {
88     super.setForeground(c);
89     if (_renderer != null) _renderer.setTextNonSelectionColor(c);
90   }
91   
92   /** Sets the background color of this tree
93    * @param c the color for the background */

94   public void setBackground(Color c) {
95     super.setBackground(c);
96     if (_renderer != null) _renderer.setBackgroundNonSelectionColor(c);
97   }
98   
99   /** Standard constructor.
100    * @param projRoot the path identifying the root node for the project
101    */

102   public JTreeSortNavigator(String JavaDoc 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 // System.err.println(isEditable());
116
}
117   
118   /** Alternate constructor specifying the display manager that provides icons for the navigator.
119    * @param projRoot the path identifying the root node for the project
120    * @param dm the display manager for the navigagtor
121    */

122   public JTreeSortNavigator(String JavaDoc projRoot, DisplayManager<? super ItemT> dm) {
123     this(projRoot);
124     _displayManager = dm;
125   }
126   
127   /** Sets the display manager that is used to select icons for the leaves of the tree.
128    * This does not apply to the inner nodes or the root.
129    */

130   public void setDisplayManager(DisplayManager<? super ItemT> manager) { _displayManager = manager; }
131   
132   /** Sets the icon to be displayed at the root of the tree */
133   public void setRootIcon(Icon ico) { _rootIcon = ico; }
134   
135   /** @return an AWT component which interacts with this document navigator */
136   public Container asContainer() { return this; }
137   
138   /** Adds an <code>IDocument</code> to this navigator. Should only executed from event thread.
139    * @param doc the document to be added into this navigator.
140    */

141   public void addDocument(ItemT doc) {
142     assert EventQueue.isDispatchThread();
143     addDocument(doc, "");
144 // GroupNode _root = null;
145
// synchronized(_model) {
146
// for (GroupNode r: _roots) {
147
// if (r.getFilter().accept(doc)) {
148
// _root = r;
149
// break;
150
// }
151
// }
152
// if (_root == null) return;
153
//
154
// LeafNode node = new LeafNode(doc);
155
// //_root.add(node);
156
// insertNodeSortedInto(node, _root);
157
// this.expandPath(new TreePath(_root.getPath()));
158
// _doc2node.put(doc, node);
159
// _hasNonProjFilesOpen = true;
160
// }
161
}
162   /** Adds an <code>INavigatorItem</code> into this navigator in the position specified by path.
163     * The actual behavior of the navigator and the position associated with a path are left up
164     * to the implementing class. Only runs in event-handling thread.
165     * @param doc the document to be added into this navigator.
166     * @param path in navigator to parent directory for doc
167     * @throws IllegalArgumentException if this navigator does not contain <code>relativeto</code> as tested by the
168     * <code>contains</code> method.
169     */

170   public void addDocument(ItemT doc, String JavaDoc path) {
171     assert EventQueue.isDispatchThread();
172     synchronized(_model) { // lock for mutation
173

174       /* Identify root matching doc if any */
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       /* Embed path in matching root, creating folder nodes if necessary */
187       StringTokenizer tok = new StringTokenizer(path, File.separator);
188       //ArrayList<String> elements = new ArrayList<String>();
189
final StringBuilder JavaDoc pathSoFarBuf = new StringBuilder JavaDoc();
190       InnerNode<?, ItemT> lastNode = root;
191       while (tok.hasMoreTokens()) {
192         String JavaDoc element = tok.nextToken();
193         pathSoFarBuf.append(element).append('/');
194         String JavaDoc pathSoFar = pathSoFarBuf.toString();
195         InnerNode<?, ItemT> thisNode;
196         //System.out.println("pathsofar = " + pathSoFar);
197
// if the node is not in the hashmap yet
198
if (!_path2node.containsKey(pathSoFar)) {
199           // make a new node
200

201           /* this inserts a folder node */
202           thisNode = new FileNode<ItemT>(new File JavaDoc(pathSoFar));
203           insertFolderSortedInto(thisNode, lastNode);
204           this.expandPath(new TreePath(lastNode.getPath()));
205           // associate the path so far with that node
206
_path2node.put(pathSoFar, thisNode);
207         }
208         else {
209           // System.out.println("path2node contains pathSoFar");
210
thisNode = _path2node.getValue(pathSoFar);
211         }
212         
213         lastNode = thisNode;
214         
215         //elements.add(element);
216
}
217       
218       /* lastNode is the node of the folder to add into */
219       
220       LeafNode<ItemT> child = new LeafNode<ItemT>(doc);
221       _doc2node.put(doc, child);
222       insertNodeSortedInto(child, lastNode);
223 // _hasNonProjFilesOpen = (lastNode == root);
224
// _model.insertNodeInto(child, lastNode, lastNode.getChildCount());
225
this.expandPath(new TreePath(lastNode.getPath()));
226       }
227   }
228   
229   private void addTopLevelGroupToRoot(InnerNode<?, ItemT> parent) {
230     assert EventQueue.isDispatchThread();
231     synchronized(_model) { // lock for mutation
232
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   /** Inserts the child node (INavigatorItem) into the sorted position as a parent node's child. Only
244    * executes in the event thread. Assumes that _model lock is already held.
245    * @param child the node to add
246    * @param parent the node to add under
247    */

248   private void insertNodeSortedInto(LeafNode<ItemT> child, InnerNode<?, ItemT> parent) {
249     int numChildren = parent.getChildCount();
250     String JavaDoc newName = child.toString();
251     String JavaDoc oldName = parent.getUserObject().toString();
252     DefaultMutableTreeNode parentsKid;
253     
254     /** Make sure that if the parent is a top level group, it is added to the tree model group. */
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         // do nothing, it's a folder
263
} 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 JavaDoc("found a node in navigator that is not an InnerNode or LeafNode");
267     }
268     _model.insertNodeInto(child, parent, i);
269   }
270   
271   /** Inserts a folder (String) into sorted position under the parent. Only executes in event thread. Assumes that
272     * _model lock is already held
273     * @param child the folder to add
274     * @param parent the folder to add under
275     */

276   private void insertFolderSortedInto(InnerNode<?, ItemT> child, InnerNode<?, ItemT> parent) {
277     int numChildren = parent.getChildCount();
278     String JavaDoc newName = child.toString();
279     String JavaDoc 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       // we're out of folders, and starting into the files, so just break out.
297
else throw new IllegalStateException JavaDoc("found a node in navigator that is not an InnerNode or LeafNode");
298     }
299     _model.insertNodeInto(child, parent, i);
300   }
301   
302   /** Removes a given <code>INavigatorItem<code> from this navigator. Removes all <code>INavigatorItem</code>s
303    * from this navigator that are "equal" (using <code>.equals(...)</code>) to the passed argument. Any of
304    * the removed documents may be returned by this method. If the NavigatorItem is found in the navigator, null
305    * is returned. Only executes from event thread.
306    * @param doc the docment to be removed
307    * @return doc a document removed from this navigator as a result of invoking this method.
308    * @throws IllegalArgumentException if this navigator contains no document equal to doc
309    */

310   public ItemT removeDocument(ItemT doc) {
311     assert EventQueue.isDispatchThread();
312     synchronized(_model) { // lock for mutation
313
LeafNode<ItemT> toRemove = getNodeForDoc(doc);
314       if (toRemove == null) return null;
315       return removeNode(getNodeForDoc(doc));
316     }
317   }
318   
319   /** Assumes lock on _model is already held or that it is being run in the event thread. */
320   private LeafNode<ItemT> getNodeForDoc(ItemT doc) {
321 // synchronized(_model) {
322
return _doc2node.get(doc);
323 // }
324
}
325   
326   /** Only takes in nodes that have an INavigatorItem as their object; assumes _model lock is already held.
327    * Only executes in event thread. */

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     // // check all elements of the tree and remove incomplete items
335
// Enumeration enumeration = ((DefaultMutableTreeNode)_model.getRoot()).depthFirstEnumeration();
336
// while(enumeration.hasMoreElements()) {
337
// TreeNode next = (TreeNode)enumeration.nextElement();
338
// if(next.getChildCount() == 0 &&
339
// !_doc2node.containsValue(next) &&
340
// next != _model.getRoot())
341
// {
342
// _model.removeNodeFromParent((MutableTreeNode)next);
343
// _path2node.removeKey((InnerNode)next);
344
// }
345
// }
346

347     
348 // if (_nonProjRoot.getChildCount() == 0) _hasNonProjFilesOpen = false;
349
return node.getData();
350   }
351   
352   /** If the given node is an InnerNode with no childrne, it removes it from the tree. If the given node is a leaf or
353     * the root, it does nothing to it. Assumes that _model lock is already held. Only executes in the event thread.
354     */

355   private void cleanFolderNode(DefaultMutableTreeNode node) {
356 // synchronized(_model) {
357
if (node instanceof InnerNode && node.getChildCount() == 0) {
358         DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
359         _model.removeNodeFromParent(node);
360         @SuppressWarnings JavaDoc("unchecked") InnerNode<?, ItemT> typedNode = (InnerNode<?, ItemT>) node;
361         _path2node.removeKey(typedNode);
362         cleanFolderNode(parent);
363       }
364 // }
365
}
366   
367   /** Resets a given <code>INavigatorItem<code> in the tree. Updates the placement of the item and its display
368     * to reflect any changes made in the model. Only executes in the event thread.
369     * Note: full synchronization commented out because this operation is only performed in the event thread. The
370     * synchronized sections must be atomic but the rest of the code can run concurrently with read operations in
371     * other threads.
372     * @param doc the document to be refreshed
373     * @param path the path to the parent folder for this document
374     * @throws IllegalArgumentException if this navigator contains no document equal to doc.
375     */

376   public void refreshDocument(ItemT doc, String JavaDoc path) {
377     assert EventQueue.isDispatchThread();
378 // synchronized(_model) {
379
LeafNode<ItemT> node = _doc2node.get(doc);
380       InnerNode<?, ?> oldParent;
381       if (node == null) { // document has not yet been entered in tree
382
addDocument(doc, path);
383 // oldParent = null;
384
return; // inserted because the sequel simply removes node and adds it back again!
385
}
386 // else { // commented out after insertion of return above
387
InnerNode<?, ?> p = (InnerNode<?, ?>) node.getParent();
388       oldParent = p;
389 // }
390

391       // Check to see if the new parent (could be same) exists already
392
String JavaDoc 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); // node that should be parent
400

401       // System.out.println("path="+path);
402
// System.out.println("newPath="+newPath);
403
// System.out.println("oldParent="+oldParent);
404
// System.out.println("newParent="+newParent);
405
// System.out.println(_path2node);
406

407       if (newParent == oldParent) { // no mutation has occurred before this point because oldParent != null
408
if (! node.toString().equals(doc.getName())) { // document has changed name?
409
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         // don't do anything if its name or parents haven't changed
417
}
418       else { // document has moved within tree
419
synchronized(_model) {
420           removeNode(node);
421           addDocument(doc, path);
422         }
423       }
424 // }
425
}
426   
427   /** Sets the specified document to be active (current). Only executes in the event thread. */
428   public void setActiveDoc(ItemT doc) {
429     assert EventQueue.isDispatchThread();
430 // synchronized (_model) { // lock out mutation
431
DefaultMutableTreeNode node = _doc2node.get(doc);
432       if (node == null) return; // doc is not in the navigator
433
if (node == _current) return; // current doc is the active doc
434
// if (_doc2node.containsKey(doc);) { // this test is obviously true since node == _doc2node.get(doc)
435
TreeNode[] nodes = node.getPath();
436         TreePath path = new TreePath(nodes);
437         expandPath(path);
438         setSelectionPath(path); // fires _gainVisitor in AbstractGlobalModel
439
scrollPathToVisible(path);
440 // }
441
// }
442
}
443   
444   /** Returns a typed equivalent to {@code next.getUserObject()}. Assumes the DefaultMutableTreeNode
445     * is a leaf node in _model and thus, if parameterized, would have type ItemT. This is a workaround for
446     * the lack of a generic implementation of TreeModel and TreeNode. If those classes become generified,
447     * this code will no longer be necessary.
448     */

449   private ItemT getNodeUserObject(DefaultMutableTreeNode n) {
450     @SuppressWarnings JavaDoc("unchecked") ItemT result = (ItemT) n.getUserObject();
451     return result;
452   }
453   
454   /** Returns the next document in the collection (using enumeration order). Executes in any thread.
455    * @param doc the INavigatorItem of interest
456    * @return the INavigatorItem which comes after doc
457    */

458   public ItemT getNext(ItemT doc) {
459     synchronized(_model) { // locks out mutation
460
DefaultMutableTreeNode node = _doc2node.get(doc);
461       if (node == null) return doc; // doc may not be contained in navigator
462
// TODO: check for "package" case
463
DefaultMutableTreeNode next = node.getNextLeaf();
464       if (next == null || next == _model.getRoot()) { return doc; }
465       else { return getNodeUserObject(next); }
466     }
467   }
468   
469   /** Returns the previous document in the collection (using enumeration order). Executes in any thread.
470    * @param doc the INavigatorItem of interest
471    * @return the INavigatorItem which comes before doc
472    */

473   public ItemT getPrevious(ItemT doc) {
474     synchronized(_model) { // locks out mutation
475
DefaultMutableTreeNode node = _doc2node.get(doc);
476       if (node == null) return doc; // doc may not be contained in navigator
477
// TODO: check for "package" case
478
DefaultMutableTreeNode prev = node.getPreviousLeaf();
479       if (prev == null || prev == _model.getRoot()) { return doc; }
480       else { return getNodeUserObject(prev); }
481     }
482   }
483   
484   /** Returns the first document in the collection (using enumeration order). Executes in any thread.
485    * @return the INavigatorItem which comes before doc
486    */

487   public ItemT getFirst() {
488     synchronized(_model) { // locks out mutation
489
DefaultMutableTreeNode root = (DefaultMutableTreeNode) _model.getRoot();
490       return getNodeUserObject(root.getFirstLeaf());
491     }
492   }
493   
494   /** Returns the last document in the collection (using enumeration order). Executes in any thread.
495    * @return the INavigatorItem which comes before doc
496    */

497   public ItemT getLast() {
498     synchronized(_model) { // locks out mutation
499
DefaultMutableTreeNode root = (DefaultMutableTreeNode) _model.getRoot();
500       return getNodeUserObject(root.getLastLeaf());
501     }
502   }
503   
504   /** Tests to see if a given document is contained in this navigator. Executes in any thread.
505    * @param doc the document to test for containment.
506    * @return <code>true</code> if this navigator contains a document that is "equal" (as tested by the
507    * <code>equals</code< method) to the passed document, else <code>false</code>.
508    */

509   public boolean contains(ItemT doc) {
510     synchronized(_model) { return _doc2node.containsKey(doc); } // locks out mutation
511
}
512   
513   /** Tests to see if a given document is contained in this navigator. Only executes in event thread.*/
514   public boolean _contains(ItemT doc) { return _doc2node.containsKey(doc); }
515   
516   /** Returns all the <code>IDocuments</code> contained in this navigator. Does not assert any type of ordering on
517    * the returned structure. Executes in any thread.
518    * @return an <code>INavigatorItem<code> enumeration of this navigator's contents.
519    */

520   public Enumeration<ItemT> getDocuments() {
521     
522     final Vector<ItemT> list = new Vector<ItemT>(getDocumentCount()); // Use Vector because it implements an Enumeration
523

524     synchronized(_model) { // locks out mutation
525
// e has a raw type because depthFirstEnumeration() has a raw type signature
526
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   /** Returns the number of <code>IDocuments</code> contained by this <code>IDocumentNavigator</code>
539     * Not synchronized on the assumption that size field of a HashMap always has a legitimate
540     * value (either the size of the current state or the size of its state before some concurrent
541     * operation started. Executes in any thread. Assume size() always returns a valid (perhaps stale) value.
542     * @return the number of documents within this navigator.
543     */

544   public int getDocumentCount() { return _doc2node.size(); }
545   
546   /** Returns whether this <code>IDocumentNavigator</code> contains any <code>IDocuments</code>.
547     * @return <code>true</code> if this navigator contains one or more documents, else <code>false</code>.
548     * Executes in any thread. Assume isEmpty() always returns a valid (perhaps stale) value.
549     */

550   public boolean isEmpty() { return _doc2node.isEmpty(); }
551   
552   /** Removes all <code>IDocuments</code> from this <code>IDocumentNavigator</code>. Only executes in event thread. */
553   public void clear() {
554     assert EventQueue.isDispatchThread();
555     synchronized(_model) {
556       _doc2node.clear();
557       ((DefaultMutableTreeNode)_model.getRoot()).removeAllChildren();
558     }
559   }
560   
561   /** Adds an <code>INavigationListener</code> to this navigator. After invoking this method, the passed
562     * listener will be eligible for observing this navigator. If the provided listener is already observing
563     * this navigator (as tested by the == operator), no action is taken. Only executes in event thread.
564     * @param listener the listener to be added to this navigator.
565     */

566   public void addNavigationListener(INavigationListener<? super ItemT> listener) {
567     assert EventQueue.isDispatchThread();
568     synchronized(_model) { navListeners.add(listener); } // locks out access during mutation
569
}
570   
571   /** Removes the given listener from observing this navigator. After invoking this method, all observers
572     * watching this navigator "equal" (as tested by the == operator) will no longer receive observable dispatches.
573     * Only executes in event thread.
574     * @param listener the listener to be removed from this navigator
575     */

576   public void removeNavigationListener(INavigationListener<? super ItemT> listener) {
577     assert EventQueue.isDispatchThread();
578     synchronized(_model) { navListeners.remove(listener); }
579   }
580   
581   /** Returns a collection of all navigator listeners. Note: this is a dangerous method since it exposes a shared data
582    * structure that must be synchronized with _model.
583    */

584   public Collection<INavigationListener<? super ItemT>> getNavigatorListeners() { return navListeners; }
585   
586   /** Standard visitor pattern. Only used within this class.
587    * @param algo the visitor to run
588    * @param input the input for the visitor
589    */

590   public <InType, ReturnType> ReturnType execute(IDocumentNavigatorAlgo<ItemT, InType, ReturnType> algo, InType input) {
591     return algo.forTree(this, input);
592   }
593   
594   /** Called whenever the value of the selection changes. Only runs in event thread. Runs _gainVisitor in global model
595    * @param e the event that characterizes the change.
596    */

597   public void valueChanged(TreeSelectionEvent JavaDoc e) {
598 // synchronized (_model) {
599
Object JavaDoc treeNode = this.getLastSelectedPathComponent();
600       if (treeNode == null || !(treeNode instanceof NodeData)) return;
601       @SuppressWarnings JavaDoc("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 // }
612
}
613   
614   /** Returns a renderer for this object. */
615   public Component getRenderer() { return _renderer; }
616   
617   /** The cell renderer for this tree. Only runs in event thread. */
618   private class CustomTreeCellRenderer extends DefaultTreeCellRenderer {
619     
620     /** Rreturns the component for a cell
621      * @param tree
622      */

623     public Component getTreeCellRendererComponent(JTree tree, Object JavaDoc 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   /** Selects the document at the x,y coordinate of the navigator pane and sets it to be the currently active
644    * document. Only runs in event thread. O
645    * @param x the x coordinate of the navigator pane
646    * @param y the y coordinate of the navigator pane
647    */

648   public boolean selectDocumentAt(int x, int y) {
649 // synchronized (_model) {
650
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 // }
675
}
676   
677   /** @return true if a group if INavigatorItems selected. Only runs in event thread. */
678   public boolean isGroupSelected() {
679 // synchronized (_model) {
680
TreePath p = getSelectionPath();
681       TreeNode n = (TreeNode) p.getLastPathComponent();
682       return (n instanceof InnerNode);
683 // }
684
}
685   
686   /** Returns true if a top level group is selected. Only runs in event thread. */
687   public boolean isTopLevelGroupSelected() {
688 // synchronized (_model) {
689
TreePath p = getSelectionPath();
690       TreeNode n = (TreeNode) p.getLastPathComponent();
691       return (n instanceof GroupNode);
692 // }
693
}
694   
695   /** Returns the name of the top level group that the selected item descends from. Only runs in event thread. */
696   public String JavaDoc 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   /** Returns the currently selected leaf node, or null if the selected node is not a leaf. Only reads a single
711     * volatile field that always has a valid value. Thread safe. */

712   public ItemT getCurrent() {
713     NodeData<ItemT> current = _current;
714     if (current == null) return null;
715     return current.execute(_leafVisitor);
716   }
717   
718   /** Returns the model lock. */
719   public Object JavaDoc getModelLock() { return _model; }
720   
721   private final NodeDataVisitor<ItemT, ItemT> _leafVisitor = new NodeDataVisitor<ItemT, ItemT>() {
722     public ItemT fileCase(File JavaDoc f, Object JavaDoc... p){ return null; }
723     public ItemT stringCase(String JavaDoc s, Object JavaDoc... p){ return null; }
724     public ItemT itemCase(ItemT ini, Object JavaDoc... p){ return ini; }
725   };
726   
727   /** @return true if the INavigatorItem is in the selected group. Only runs in event thread. */
728   public boolean isSelectedInGroup(ItemT i) {
729 // synchronized (_model) {
730
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 // }
743
}
744   
745   /** Adds a top level group to the navigator. Only runs in event thread. */
746   public void addTopLevelGroup(String JavaDoc name, INavigatorItemFilter<? super ItemT> f){
747     if (f == null)
748       throw new IllegalArgumentException JavaDoc("parameter 'f' is not allowed to be null");
749     GroupNode<ItemT> n = new GroupNode<ItemT>(name, f);
750     _roots.add(n);
751   }
752   
753   /******* Methods that handle expansion/collapsing of folders in tree **********/
754   
755   /** Called whenever an item in the tree has been collapsed. Only runs in event thread. */
756   public void treeCollapsed(TreeExpansionEvent JavaDoc event) {
757     Object JavaDoc o = event.getPath().getLastPathComponent();
758     if (o instanceof InnerNode) ((InnerNode<?, ?>)o).setCollapsed(true);
759   }
760   
761   /** Called whenever an item in the tree has been expanded. Only runs in event thread. */
762   public void treeExpanded(TreeExpansionEvent JavaDoc event) {
763     Object JavaDoc o = event.getPath().getLastPathComponent();
764     if (o instanceof InnerNode) ((InnerNode<?, ?>)o).setCollapsed(false);
765   }
766   
767   /** Collapses all the paths in the tree that match one of the path strings included in the given hash set. Path
768     * strings must follow a specific format in order for them to work. See the documentation of
769     * <code>generatePathString</code> for information on the format of the path strings. Only executes in event thread.
770     * @param paths A hash set of path strings.
771     */

772   public void collapsePaths(String JavaDoc[] paths) {
773     assert EventQueue.isDispatchThread();
774     
775     HashSet<String JavaDoc> set = new HashSet<String JavaDoc>();
776     for (String JavaDoc s : paths) { set.add(s); }
777     collapsePaths(set);
778   }
779
780   /** Set variation of collapsePaths(String ...). Private except for testing code. Only runs in event thread except
781     * for testing code.
782     */

783   void collapsePaths(HashSet<String JavaDoc> paths) {
784     
785     DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)_model.getRoot();
786     // We use a raw type here because depthFirstEnumeration() has a raw type signature
787
Enumeration nodes = rootNode.depthFirstEnumeration();
788     ArrayList<String JavaDoc> list = new ArrayList<String JavaDoc>();
789     while (nodes.hasMoreElements()) {
790       DefaultMutableTreeNode tn = (DefaultMutableTreeNode)nodes.nextElement();
791       if (tn instanceof InnerNode) {
792         TreePath tp = new TreePath(tn.getPath());
793         String JavaDoc s = generatePathString(tp);
794         boolean shouldCollapse = paths.contains(s);
795         if (shouldCollapse) {
796           collapsePath(tp);
797         }
798       }
799     }
800   }
801   
802   /** @return an array of path strings corresponding to the paths of the tree nodes that
803     * are currently collapsed. See the documentation of <code>generatePathString</code>
804     * for information on the format of the path strings. Only runs in event thread.
805     */

806   public String JavaDoc[] getCollapsedPaths() {
807     ArrayList<String JavaDoc> list = new ArrayList<String JavaDoc>();
808 // synchronized (_model) {
809
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)_model.getRoot();
810       // We use a raw type here because depthFirstEnumeration() has a raw type signature
811
Enumeration nodes = rootNode.depthFirstEnumeration(); /** This warning is expected **/
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 // }
820
return list.toArray(new String JavaDoc[list.size()]);
821   }
822   
823   /** Generates a path string for the given tree node. <p>The path string does not include the project
824     * root node, but rather a period in its place. Following the "./" is one of the 3 main groups,
825     * "[ Source Files ]", "[ Auxiliary ]", "[ External ]". The nodes in the path are represented by their
826     * names delimited by the forward slash ("/"). The path ends with a final delimeter.
827     * (e.g. "./[ Source Files ]/util/docnavigation/") Only runs in event thread.
828     * @return the path string for the given node in the JTree
829     */

830   public String JavaDoc generatePathString(TreePath tp) {
831     String JavaDoc path = "";
832 // synchronized (_model) {
833
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 // }
841
}
842     
843     return path;
844   }
845   
846   /** If the currently selected item is not an INavigatorItem, select the one given. Only runs in event thread. */
847   public void requestSelectionUpdate(ItemT ini) {
848 // synchronized (_model) {
849
if (getCurrent() == null) { // the currently selected node is not a leaf
850
setActiveDoc(ini);
851       }
852 // }
853
}
854   
855   /** Marks the next selection change as model-initiated (true) or user-initiated (false; default). */
856   public void setNextChangeModelInitiated(boolean b) {
857     putClientProperty(MODEL_INITIATED_PROPERTY_NAME, b?Boolean.TRUE:null);
858   }
859   
860   /** @return whether the next selection change is model-initiated (true) or user-initiated (false). */
861   public boolean isNextChangeModelInitiated() {
862     return getClientProperty(MODEL_INITIATED_PROPERTY_NAME) != null;
863   }
864   
865 // /** Unnecessary since "modified" mark is added by the cell renderer */
866
// public void activeDocumentModified() { }
867
}
868
869
Popular Tags