001: /*
002: * Copyright 2008 Paul Hinds
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.tp23.gui.tree;
017:
018: import java.awt.Color;
019: import java.awt.Rectangle;
020: import java.awt.event.ActionEvent;
021: import java.awt.event.MouseAdapter;
022: import java.awt.event.MouseEvent;
023: import java.io.File;
024: import java.io.FileNotFoundException;
025: import java.io.FilenameFilter;
026: import java.util.ArrayList;
027: import java.util.Arrays;
028: import java.util.Enumeration;
029: import java.util.Iterator;
030: import java.util.LinkedList;
031: import java.util.List;
032:
033: import javax.swing.JLabel;
034: import javax.swing.JTree;
035: import javax.swing.event.TreeExpansionEvent;
036: import javax.swing.event.TreeExpansionListener;
037: import javax.swing.event.TreeWillExpandListener;
038: import javax.swing.tree.DefaultMutableTreeNode;
039: import javax.swing.tree.DefaultTreeModel;
040: import javax.swing.tree.MutableTreeNode;
041: import javax.swing.tree.TreeModel;
042: import javax.swing.tree.TreeNode;
043: import javax.swing.tree.TreePath;
044: import javax.swing.tree.TreeSelectionModel;
045:
046: import org.tp23.gui.tree.model.FileSystemModel;
047: import org.tp23.gui.tree.model.ItemModel;
048:
049: /**
050: * JTree for exploring a filesystem, currently there are callbacks for double clicking files
051: * but double clicking directories is handled internally to mean refresh the directory view from the file system
052: * @author teknopaul
053: *
054: */
055: public class FileTreeSelector extends JTree {
056:
057: private int iconWidth = 1;
058: private DirectoryTreeCellRenderer dirRenderer;
059: private boolean refreshOnCollapse = false;
060: private boolean showLeafs = false;
061: private FileActionListener[] listeners = new FileActionListener[0];
062: private File defaultDirectory;
063:
064: public FileTreeSelector(boolean showFiles) {
065: this .showLeafs = showFiles;
066: setRootVisible(true);
067: dirRenderer = new DirectoryTreeCellRenderer();
068: this .setCellRenderer(dirRenderer);
069: this .setEditable(false);
070: iconWidth = dirRenderer.getIconWidth();
071: getSelectionModel().setSelectionMode(
072: TreeSelectionModel.SINGLE_TREE_SELECTION);
073: }
074:
075: public FileTreeSelector(ItemModel rootDirectory, boolean showFiles) {
076: this (showFiles);
077: setRoot(rootDirectory);
078: }
079:
080: /**
081: * @return The selected file or null
082: */
083: public File getSelectedFile() {
084: if (getSelectionPath() == null) {
085: return null;
086: }
087: DefaultMutableTreeNode node = (DefaultMutableTreeNode) getSelectionPath()
088: .getLastPathComponent();
089: FileSystemModel model = (FileSystemModel) node.getUserObject();
090: if (model.getData() instanceof FileSystemModel.MockFile) {
091: return null;
092: }
093: return (File) model.getData();
094: }
095:
096: public TreePath setDefaultDirectory(File dirPath) {
097: return locateDirectory(dirPath.getAbsolutePath());
098: }
099:
100: /**
101: * setDefaultDirectory may refuse if the directory does not exist
102: * this method when called directly after setDefault returns the
103: * directory that was chosen
104: * @return
105: */
106: public File getDefaultDirectory() {
107: return defaultDirectory;
108: }
109:
110: /**
111: * Same as locateAndView for directories (or user home if file nor parent are available)
112: */
113: public TreePath locateDirectory(String filename) {
114:
115: boolean expandLast = false;
116: File requested = new File(filename);
117: if (requested.exists()) {
118: defaultDirectory = requested;
119: expandLast = false;
120: }
121: if ((!requested.exists()) && requested.getParentFile().exists()) {
122: defaultDirectory = requested;
123: expandLast = true;
124: } else {
125: File userHome = new File(System.getProperty("user.home"));
126: filename = userHome.getAbsolutePath();
127: defaultDirectory = userHome;
128: expandLast = true;
129: }
130:
131: String[] paths = splitPath(filename);
132: TreeModel treeModel = this .getModel();
133: DirectoryMutableTreeNode currentNode = (DirectoryMutableTreeNode) treeModel
134: .getRoot();
135: TreePath treePath = new TreePath(currentNode);
136: for (int i = 1; i < paths.length; i++) {
137: String pathitem = paths[i];
138: if (pathitem.length() == 0) {
139: continue;
140: }
141: if (!currentNode.isLoaded()) {
142: loadDirectory(currentNode);
143: }
144: DirectoryMutableTreeNode nextNode = getSubDirNode(
145: currentNode, pathitem);
146: // win fix for x:\ only path
147: if (nextNode == null) {
148: break;
149: } else {
150: currentNode = nextNode;
151: }
152: treePath = treePath.pathByAddingChild(currentNode);
153: }
154:
155: if (expandLast) {
156: expandPath(treePath);
157: this .setSelectionPath(treePath);
158: return treePath;
159: } else {
160: expandPath(treePath.getParentPath());// expand only directoruies
161: this .setSelectionPath(treePath.getParentPath());
162: return treePath;
163: }
164:
165: }
166:
167: public DirectoryMutableTreeNode getSubDirNode(TreeNode current,
168: String nextPath) {
169: Enumeration enumeration = current.children();
170: while (enumeration.hasMoreElements()) {
171: MutableTreeNode element = (MutableTreeNode) enumeration
172: .nextElement();
173: if (element instanceof DirectoryMutableTreeNode) {
174: DirectoryMutableTreeNode dir = (DirectoryMutableTreeNode) element;
175: ItemModel dirModel = (ItemModel) dir.getUserObject();
176: if (dirModel.getName().equals(nextPath)) {
177: return dir;
178: }
179: }
180: }
181: return null;
182: }
183:
184: public void setRoot(ItemModel rootDirectory) {
185: if (rootDirectory.isLeaf()) {
186: throw new IllegalArgumentException(
187: "The Root of a FileTreeSelector must be a directory");
188: }
189: DirectoryMutableTreeNode top = new DirectoryMutableTreeNode(
190: rootDirectory);
191:
192: DefaultTreeModel treeModel = new DefaultTreeModel(top);
193: this .setModel(treeModel);
194: //loadDirectory(top);
195: this .setEditable(false);
196: this .addTreeWillExpandListener(new TreeWillExpandListener() {
197: public void treeWillCollapse(TreeExpansionEvent event) {
198: }
199:
200: public void treeWillExpand(TreeExpansionEvent event) {
201: DirectoryMutableTreeNode node = (DirectoryMutableTreeNode) event
202: .getPath().getLastPathComponent();
203: if (!node.isLoaded()) {
204: loadDirectory(node);
205: }
206: }
207: });
208: this .addTreeExpansionListener(new TreeExpansionListener() {
209: public void treeCollapsed(TreeExpansionEvent event) {
210: if (refreshOnCollapse) {
211: DirectoryMutableTreeNode node = (DirectoryMutableTreeNode) event
212: .getPath().getLastPathComponent();
213: if (node.isLoaded()) {
214: node.removeAllChildren();
215: node.setLoaded(false);
216: }
217: }
218: }
219:
220: public void treeExpanded(TreeExpansionEvent event) {
221: }
222: });
223:
224: this .addMouseListener(new MouseAdapter() {
225: public void mouseClicked(MouseEvent me) {
226: TreePath path = FileTreeSelector.this
227: .getClosestPathForLocation(me.getX(), me.getY());
228: DefaultMutableTreeNode node = (DefaultMutableTreeNode) path
229: .getLastPathComponent();
230: if (node == null)
231: return;
232: Rectangle toRepaint = FileTreeSelector.this
233: .getPathBounds(path);
234: int xOffset = me.getX() - toRepaint.x;
235: if (xOffset < 0) {
236: FileTreeSelector.this .setEditable(false);
237: } else if (xOffset < iconWidth) {
238: //use for clicking the folder icon
239: me.setSource(null);
240: } else {
241: if (node instanceof FileMutableTreeNode
242: && me.getClickCount() > 1) {
243: ActionEvent ae = new ActionEvent(node,
244: ActionEvent.ACTION_PERFORMED,
245: "File Selected");
246: fireFileActionListeners(ae);
247: } else if (node instanceof DirectoryMutableTreeNode
248: && me.getClickCount() > 1) {
249: refreshDirectory((DirectoryMutableTreeNode) node);
250: }
251: }
252: }
253: });
254:
255: }
256:
257: public void loadDirectory(DirectoryMutableTreeNode current) {
258: ItemModel directory = (ItemModel) current.getUserObject();
259: ItemModel[] files;
260: files = directory.getChildren();
261:
262: Arrays.sort(files);
263: for (int i = 0; i < files.length; i++) {
264: if (!files[i].isLeaf()) {
265: DirectoryMutableTreeNode child = new DirectoryMutableTreeNode(
266: files[i]);
267: current.add(child);
268: }
269: }
270: if (showLeafs) {
271: for (int i = 0; i < files.length; i++) {
272: if (files[i].isLeaf()) {
273: FileMutableTreeNode child = new FileMutableTreeNode(
274: files[i]);
275: current.add(child);
276: }
277: }
278: }
279: current.setLoaded(true);
280: ((DefaultTreeModel) treeModel).nodeStructureChanged(current);
281: }
282:
283: public void refreshDirectory(DirectoryMutableTreeNode current) {
284: ItemModel directory = (ItemModel) current.getUserObject();
285: ItemModel[] files;
286: files = directory.getChildren();
287: Arrays.sort(files);
288:
289: DefaultMutableTreeNode next = null; // here so it stays once on the stack
290: DirectoryMutableTreeNode childDir = null;
291: FileMutableTreeNode childFile = null;
292: List chosen = new ArrayList();
293:
294: current.removeAllChildren();
295: if (showLeafs) {
296: for (int i = 0; i < files.length; i++) {
297: if (files[i].isLeaf()) {
298: childFile = new FileMutableTreeNode(files[i]);
299: current.add(childFile);
300: }
301: }
302: }
303: ((DefaultTreeModel) treeModel).nodeStructureChanged(current);
304: }
305:
306: public void addFileActionListener(FileActionListener listener) {
307: FileActionListener[] newListerners = new FileActionListener[listeners.length + 1];
308: System.arraycopy(listeners, 0, newListerners, 0,
309: listeners.length);
310: newListerners[listeners.length] = listener;
311: listeners = newListerners;
312: }
313:
314: /*
315: public void addFileSelectionListener(FileSelectionListener listener){
316: //TODO
317: }
318: */
319: public void fireFileActionListeners(ActionEvent ae) {
320: for (int i = 0; i < listeners.length; i++) {
321: listeners[i].actionPerformed(ae);
322: }
323: }
324:
325: public void fireFileSelectionListeners(ActionEvent ae) {
326: //TODO
327: }
328:
329: public Color getBackground() {
330: return new JLabel().getBackground();
331: }
332:
333: public boolean isRefreshOnCollapse() {
334: return refreshOnCollapse;
335: }
336:
337: public void setRefreshOnCollapse(boolean refreshOnCollapse) {
338: this .refreshOnCollapse = refreshOnCollapse;
339: }
340:
341: private String[] splitPath(String relativePath) {
342: String separator = File.separator;
343: String[] paths = null;
344: if (separator.equals("/")) {
345: paths = relativePath.split("/");
346: } else { // windoze
347: paths = relativePath.split("\\\\");
348: }
349: return paths;
350:
351: }
352:
353: }
|