/Users/lyon/j4p/src/gui/tree/FileTree.java
|
1 package gui.tree;
2
3 import javax.swing.*;
4 import javax.swing.event.TreeExpansionEvent;
5 import javax.swing.event.TreeExpansionListener;
6 import javax.swing.tree.DefaultMutableTreeNode;
7 import javax.swing.tree.DefaultTreeModel;
8 import javax.swing.tree.TreeModel;
9 import javax.swing.tree.TreePath;
10 import java.awt.*;
11 import java.awt.dnd.Autoscroll;
12 import java.io.File;
13 import java.io.FileNotFoundException;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Comparator;
17
18 public class FileTree extends JTree implements Autoscroll {
19 public static final Insets defaultScrollInsets = new Insets(8, 8, 8, 8);
20 protected Insets scrollInsets = defaultScrollInsets;
21
22 public FileTree(String path) throws FileNotFoundException, SecurityException {
23 super((TreeModel) null); // Create the JTree itself
24
25 // Use horizontal and vertical lines
26 putClientProperty("JTree.lineStyle", "Angled");
27
28 // Create the first node
29 DefaultMutableFileTreeNode rootNode = new DefaultMutableFileTreeNode(null, path);
30
31 // Populate the root node with its subdirectories
32 boolean addedNodes = rootNode.populateDirectories(true);
33 setModel(new DefaultTreeModel(rootNode));
34
35 // Listen for Tree Selection Events
36 addTreeExpansionListener(new TreeExpansionHandler());
37 }
38
39 // Returns the full pathname for a path, or null if not a known path
40 public String getPathName(TreePath path) {
41 Object o = path.getLastPathComponent();
42 if (o instanceof DefaultMutableFileTreeNode) {
43 return ((DefaultMutableFileTreeNode) o).fullName;
44 }
45 return null;
46 }
47
48 // Adds a new node to the gui.tree after construction.
49 // Returns the inserted node, or null if the parent
50 // directory has not been expanded.
51 public DefaultMutableFileTreeNode addNode(DefaultMutableFileTreeNode parent, String name) {
52 int index = parent.addNode(name);
53 if (index != -1) {
54 ((DefaultTreeModel) getModel()).nodesWereInserted(
55 parent, new int[]{index});
56 return (DefaultMutableFileTreeNode) parent.getChildAt(index);
57 }
58
59 // No node was created
60 return null;
61 }
62
63 // Autoscrolling support
64 public void setScrollInsets(Insets insets) {
65 this.scrollInsets = insets;
66 }
67
68 public Insets getScrollInsets() {
69 return scrollInsets;
70 }
71
72 // Implementation of Autoscroll interface
73 public Insets getAutoscrollInsets() {
74 Rectangle r = getVisibleRect();
75 Dimension size = getSize();
76 Insets i = new Insets(r.y + scrollInsets.top, r.x + scrollInsets.left,
77 size.height - r.y - r.height + scrollInsets.bottom,
78 size.width - r.x - r.width + scrollInsets.right);
79 return i;
80 }
81
82 public void autoscroll(Point location) {
83 JScrollPane scroller =
84 (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, this);
85 if (scroller != null) {
86 JScrollBar hBar = scroller.getHorizontalScrollBar();
87 JScrollBar vBar = scroller.getVerticalScrollBar();
88 Rectangle r = getVisibleRect();
89 if (location.x <= r.x + scrollInsets.left) {
90 // Need to scroll left
91 hBar.setValue(hBar.getValue() - hBar.getUnitIncrement(-1));
92 }
93 if (location.y <= r.y + scrollInsets.top) {
94 // Need to scroll up
95 vBar.setValue(vBar.getValue() - vBar.getUnitIncrement(-1));
96 }
97 if (location.x >= r.x + r.width - scrollInsets.right) {
98 // Need to scroll right
99 hBar.setValue(hBar.getValue() + hBar.getUnitIncrement(1));
100 }
101 if (location.y >= r.y + r.height - scrollInsets.bottom) {
102 // Need to scroll down
103 vBar.setValue(vBar.getValue() + vBar.getUnitIncrement(1));
104 }
105 }
106 }
107
108 // Inner class that represents a node in this file system gui.tree
109 public static class DefaultMutableFileTreeNode extends DefaultMutableTreeNode {
110 public DefaultMutableFileTreeNode(String parent, String name) throws SecurityException,
111 FileNotFoundException {
112 this.name = name;
113
114 // See if this node exists and whether it is a directory
115 fullName = parent == null ? name : parent + File.separator + name;
116
117 File f = new File(fullName);
118 if (f.exists() == false) {
119 throw new FileNotFoundException("File " + fullName + " does not exist");
120 }
121
122 isDir = f.isDirectory();
123
124 // Hack for Windows which doesn't consider a drive to be a directory!
125 if (isDir == false && f.isFile() == false) {
126 isDir = true;
127 }
128 }
129
130 // Override isLeaf to check whether this is a directory
131 public boolean isLeaf() {
132 return !isDir;
133 }
134
135 // Override getAllowsChildren to check whether this is a directory
136 public boolean getAllowsChildren() {
137 return isDir;
138 }
139
140 // Return whether this is a directory
141 public boolean isDir() {
142 return isDir;
143 }
144
145 // Get full path
146 public String getFullName() {
147 return fullName;
148 }
149
150 // For display purposes, we return our own name
151 public String toString() {
152 return name;
153 }
154
155 // If we are a directory, scan our contents and populate
156 // with children. In addition, populate those children
157 // if the "descend" flag is true. We only descend once,
158 // to avoid recursing the whole subtree.
159 // Returns true if some nodes were added
160 boolean populateDirectories(boolean descend) {
161 boolean addedNodes = false;
162
163 // Do this only once
164 if (populated == false) {
165 File f;
166 try {
167 f = new File(fullName);
168 } catch (SecurityException e) {
169 populated = true;
170 return false;
171 }
172
173 if (interim == true) {
174 // We have had a quick look here before:
175 // remove the dummy node that we added last time
176 removeAllChildren();
177 interim = false;
178 }
179
180 String fileNames[] = f.list(); // Get list of contents
181
182 // Process the contents
183 ArrayList al = new ArrayList();
184 for (int i = 0; i < fileNames.length; i++) {
185 String fileName = fileNames[i];
186 File d = new File(fullName, fileName);
187 try {
188 DefaultMutableFileTreeNode node =
189 new DefaultMutableFileTreeNode(fullName, fileName);
190 al.add(node);
191 if (descend && d.isDirectory()) {
192 node.populateDirectories(false);
193 }
194 addedNodes = true;
195 if (descend == false) {
196 // Only add one node if not descending
197 break;
198 }
199 } catch (Throwable t) {
200 // Ignore phantoms or access problems
201 }
202 }
203
204 if (addedNodes == true) {
205 // Now sort the list of contained files and directories
206 Object[] nodes = al.toArray();
207 Arrays.sort(nodes, new Comparator() {
208 public boolean equals(Object o) {
209 return false;
210 }
211
212 public int compare(Object o1, Object o2) {
213 DefaultMutableFileTreeNode node1 = (DefaultMutableFileTreeNode) o1;
214 DefaultMutableFileTreeNode node2 = (DefaultMutableFileTreeNode) o2;
215
216 // Directories come first
217 if (node1.isDir != node2.isDir) {
218 return node1.isDir ? -1 : +1;
219 }
220
221 // Both directories or both files -
222 // compare based on pathname
223 return node1.fullName.compareTo(node2.fullName);
224 }
225 });
226
227 // Add sorted items as children of this node
228 for (int j = 0; j < nodes.length; j++) {
229 this.add((DefaultMutableFileTreeNode) nodes[j]);
230 }
231 }
232
233 // If we were scanning to get all subdirectories,
234 // or if we found no content, there is no
235 // reason to look at this directory again, so
236 // set populated to true. Otherwise, we set interim
237 // so that we look again in the future if we need to
238 if (descend == true || addedNodes == false) {
239 populated = true;
240 } else {
241 // Just set interim state
242 interim = true;
243 }
244 }
245 return addedNodes;
246 }
247
248 // Adding a new file or directory after
249 // constructing the FileTree. Returns
250 // the index of the inserted node.
251 public int addNode(String name) {
252 // If not populated yet, do nothing
253 if (populated == true) {
254 // Do not add a new node if
255 // the required node is already there
256 int childCount = getChildCount();
257 for (int i = 0; i < childCount; i++) {
258 DefaultMutableFileTreeNode node = (DefaultMutableFileTreeNode) getChildAt(i);
259 if (node.name.equals(name)) {
260 // Already exists - ensure
261 // we repopulate
262 if (node.isDir()) {
263 node.interim = true;
264 node.populated = false;
265 }
266 return -1;
267 }
268 }
269
270 // Add a new node
271 try {
272 DefaultMutableFileTreeNode node = new DefaultMutableFileTreeNode(fullName, name);
273 add(node);
274 return childCount;
275 } catch (Exception e) {
276 }
277 }
278 return -1;
279 }
280
281 protected String name; // Name of this component
282 protected String fullName; // Full pathname
283 protected boolean populated;// true if we have been populated
284 protected boolean interim; // true if we are in interim state
285 protected boolean isDir; // true if this is a directory
286 }
287
288 // Inner class that handles Tree Expansion Events
289 protected class TreeExpansionHandler implements TreeExpansionListener {
290 public void treeExpanded(TreeExpansionEvent evt) {
291 TreePath path = evt.getPath(); // The expanded path
292 JTree tree = (JTree) evt.getSource(); // The gui.tree
293
294 // Get the last component of the path and
295 // arrange to have it fully populated.
296 DefaultMutableFileTreeNode node = (DefaultMutableFileTreeNode) path.getLastPathComponent();
297 if (node.populateDirectories(true)) {
298 ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
299 }
300 }
301
302 public void treeCollapsed(TreeExpansionEvent evt) {
303 // Nothing to do
304 }
305 }
306 }