PageRenderTime 56ms CodeModel.GetById 27ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-0-pre3/org/gjt/sp/jedit/browser/BrowserView.java

#
Java | 784 lines | 587 code | 100 blank | 97 comment | 112 complexity | 9aabe6b1a74792d1183b6a3e7595e11e MD5 | raw file
  1/*
  2 * BrowserView.java
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2000 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit.browser;
 24
 25//{{{ Imports
 26import javax.swing.border.EmptyBorder;
 27import javax.swing.event.*;
 28import javax.swing.tree.*;
 29import javax.swing.*;
 30import java.awt.event.*;
 31import java.awt.*;
 32import java.io.File;
 33import java.util.Enumeration;
 34import java.util.Hashtable;
 35import java.util.Vector;
 36import org.gjt.sp.jedit.io.*;
 37import org.gjt.sp.jedit.*;
 38//}}}
 39
 40/**
 41 * VFS browser tree view.
 42 * @author Slava Pestov
 43 * @version $Id: BrowserView.java 3936 2001-12-21 07:02:14Z spestov $
 44 */
 45public class BrowserView extends JPanel
 46{
 47	//{{{ BrowserView constructor
 48	public BrowserView(VFSBrowser browser)
 49	{
 50		this.browser = browser;
 51
 52		parentModel = new DefaultListModel();
 53		parentDirectories = new JList(parentModel);
 54
 55		parentDirectories.getSelectionModel().setSelectionMode(
 56			TreeSelectionModel.SINGLE_TREE_SELECTION);
 57
 58		parentDirectories.setCellRenderer(new ParentDirectoryRenderer());
 59		parentDirectories.setVisibleRowCount(5);
 60		parentDirectories.addMouseListener(new MouseHandler());
 61
 62		rootNode = new DefaultMutableTreeNode(null,true);
 63		model = new DefaultTreeModel(rootNode,true);
 64
 65		tree = new BrowserJTree(model);
 66		tree.setCellRenderer(renderer);
 67		tree.setEditable(false);
 68		tree.addTreeExpansionListener(new TreeHandler());
 69		tree.putClientProperty("JTree.lineStyle", "Angled");
 70		tree.setRootVisible(false);
 71		tree.setShowsRootHandles(true);
 72		tree.setVisibleRowCount(12);
 73
 74		JScrollPane parentScroller = new JScrollPane(parentDirectories);
 75		parentScroller.setMinimumSize(new Dimension(0,0));
 76		JScrollPane treeScroller = new JScrollPane(tree);
 77		treeScroller.setMinimumSize(new Dimension(0,0));
 78		splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
 79			parentScroller,treeScroller);
 80		// not null, because then it would be reset to the
 81		// default by updateUI()
 82		splitPane.setBorder(new EmptyBorder(0,0,0,0));
 83
 84		tmpExpanded = new Hashtable();
 85
 86		if(browser.isMultipleSelectionEnabled())
 87			tree.getSelectionModel().setSelectionMode(
 88				TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
 89		else
 90			tree.getSelectionModel().setSelectionMode(
 91				TreeSelectionModel.SINGLE_TREE_SELECTION);
 92
 93		setLayout(new BorderLayout());
 94
 95		add(BorderLayout.CENTER,splitPane);
 96
 97		propertiesChanged();
 98	} //}}}
 99
100	//{{{ requestDefaultFocus() method
101	public boolean requestDefaultFocus()
102	{
103		tree.requestFocus();
104		return true;
105	} //}}}
106
107	//{{{ addNotify() method
108	public void addNotify()
109	{
110		super.addNotify();
111
112		SwingUtilities.invokeLater(new Runnable()
113		{
114			public void run()
115			{
116				int loc = jEdit.getIntegerProperty(
117					"vfs.browser.splitter",0);
118				if(loc != 0)
119				{
120					splitPane.setDividerLocation(loc);
121					parentDirectories.ensureIndexIsVisible(
122						parentDirectories.getModel()
123						.getSize());
124				}
125			}
126		});
127	} //}}}
128
129	//{{{ removeNotify() method
130	public void removeNotify()
131	{
132		jEdit.setIntegerProperty("vfs.browser.splitter",
133			splitPane.getDividerLocation());
134
135		super.removeNotify();
136	} //}}}
137
138	//{{{ getSelectedFiles() method
139	public VFS.DirectoryEntry[] getSelectedFiles()
140	{
141		Vector selected = new Vector(tree.getSelectionCount());
142		TreePath[] paths = tree.getSelectionPaths();
143		if(paths == null)
144			return new VFS.DirectoryEntry[0];
145
146		for(int i = 0; i < paths.length; i++)
147		{
148			DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)
149				paths[i].getLastPathComponent();
150			Object obj = treeNode.getUserObject();
151			if(obj instanceof VFS.DirectoryEntry)
152				selected.addElement(obj);
153		}
154
155		VFS.DirectoryEntry[] retVal = new VFS.DirectoryEntry[selected.size()];
156		selected.copyInto(retVal);
157		return retVal;
158	} //}}}
159
160	//{{{ selectNone() method
161	public void selectNone()
162	{
163		tree.setSelectionPaths(new TreePath[0]);
164	} //}}}
165
166	//{{{ directoryLoaded() method
167	public void directoryLoaded(String path, Vector directory)
168	{
169		if(currentlyLoadingTreeNode == rootNode)
170		{
171			parentModel.removeAllElements();
172			String parent = path;
173			for(;;)
174			{
175				parentModel.insertElementAt(parent,0);
176				String newParent = MiscUtilities.getParentOfPath(parent);
177				if(newParent.length() != 1 && (newParent.endsWith("/")
178					|| newParent.endsWith(File.separator)))
179					newParent = newParent.substring(0,newParent.length() - 1);
180
181				if(newParent == null || parent.equals(newParent))
182					break;
183				else
184					parent = newParent;
185			}
186
187			int index = parentModel.getSize() - 1;
188			parentDirectories.setSelectedIndex(index);
189			parentDirectories.ensureIndexIsVisible(parentModel.getSize() - 1);
190		}
191
192		currentlyLoadingTreeNode.removeAllChildren();
193
194		Vector toExpand = new Vector();
195
196		if(directory != null)
197		{
198			for(int i = 0; i < directory.size(); i++)
199			{
200				VFS.DirectoryEntry file = (VFS.DirectoryEntry)
201					directory.elementAt(i);
202				boolean allowsChildren = (file.type != VFS.DirectoryEntry.FILE);
203				DefaultMutableTreeNode node = new DefaultMutableTreeNode(file,allowsChildren);
204				currentlyLoadingTreeNode.add(node);
205				if(tmpExpanded.get(file.path) != null)
206					toExpand.addElement(new TreePath(node.getPath()));
207			}
208		}
209
210		tmpExpanded.clear();
211
212		// fire events
213		model.reload(currentlyLoadingTreeNode);
214		tree.expandPath(new TreePath(currentlyLoadingTreeNode.getPath()));
215
216		// expand branches that were expanded before
217		for(int i = 0; i < toExpand.size(); i++)
218		{
219			TreePath treePath = (TreePath)toExpand.elementAt(i);
220			tree.expandPath(treePath);
221		}
222
223		timer.stop();
224		typeSelectBuffer.setLength(0);
225	} //}}}
226
227	//{{{ updateFileView() method
228	public void updateFileView()
229	{
230		tree.repaint();
231	} //}}}
232
233	//{{{ loadDirectory() method
234	public void loadDirectory(String path)
235	{
236		loadDirectory(rootNode,path,false);
237	} //}}}
238
239	//{{{ maybeReloadDirectory() method
240	public void maybeReloadDirectory(String path)
241	{
242		// because this method is called for *every* VFS update,
243		// we don't want to scan the tree all the time. So we
244		// use the following algorithm to determine if the path
245		// might be part of the tree:
246		// - if the path starts with the browser's current directory,
247		//   we do the tree scan
248		// - if the browser's directory is 'favorites:' -- we have to
249		//   do the tree scan, as every path can appear under the
250		//   favorites list
251		// - if the browser's directory is 'roots:' and path is on
252		//   the local filesystem, do a tree scan
253		String browserDir = browser.getDirectory();
254		if(browserDir.startsWith(FavoritesVFS.PROTOCOL))
255			maybeReloadDirectory(rootNode,path);
256		else if(browserDir.startsWith(FileRootsVFS.PROTOCOL))
257		{
258			if(!MiscUtilities.isURL(path) || MiscUtilities.getProtocolOfURL(path)
259				.equals("file"))
260				maybeReloadDirectory(rootNode,path);
261		}
262		else if(path.startsWith(browserDir))
263			maybeReloadDirectory(rootNode,path);
264	} //}}}
265
266	//{{{ getDefaultFocusComponent() method
267	public Component getDefaultFocusComponent()
268	{
269		return tree;
270	} //}}}
271
272	//{{{ propertiesChanged() method
273	public void propertiesChanged()
274	{
275		showIcons = jEdit.getBooleanProperty("vfs.browser.showIcons");
276		renderer.propertiesChanged();
277
278		tree.setRowHeight(renderer.getTreeCellRendererComponent(
279			tree,new DefaultMutableTreeNode("foo"),
280			false,false,false,0,false).getSize().height);
281
282		splitPane.setBorder(null);
283	} //}}}
284
285	//{{{ Private members
286
287	//{{{ Instance variables
288	private VFSBrowser browser;
289
290	private JSplitPane splitPane;
291	private JList parentDirectories;
292	private DefaultListModel parentModel;
293	private JTree tree;
294	private Hashtable tmpExpanded;
295	private DefaultTreeModel model;
296	private DefaultMutableTreeNode rootNode;
297	private DefaultMutableTreeNode currentlyLoadingTreeNode;
298	private BrowserCommandsMenu popup;
299	private boolean showIcons;
300
301	private FileCellRenderer renderer = new FileCellRenderer();
302
303	private StringBuffer typeSelectBuffer = new StringBuffer();
304	private Timer timer = new Timer(0,new ClearTypeSelect());
305	//}}}
306
307	//{{{ maybeReloadDirectory() method
308	private boolean maybeReloadDirectory(DefaultMutableTreeNode node, String path)
309	{
310		// nodes which are not expanded need not be checked
311		if(!tree.isExpanded(new TreePath(node.getPath())))
312			return false;
313
314		if(node == rootNode && path.equals(browser.getDirectory()))
315		{
316			loadDirectory(rootNode,path,false);
317			return true;
318		}
319
320		Object userObject = node.getUserObject();
321		if(userObject instanceof VFS.DirectoryEntry)
322		{
323			VFS.DirectoryEntry file = (VFS.DirectoryEntry)userObject;
324
325			// we don't need to do anything with files!
326			if(file.type == VFS.DirectoryEntry.FILE)
327				return false;
328
329			if(path.equals(file.path))
330			{
331				loadDirectory(node,path,false);
332				return true;
333			}
334		}
335
336		if(node.getChildCount() != 0)
337		{
338			Enumeration children = node.children();
339			while(children.hasMoreElements())
340			{
341				DefaultMutableTreeNode child = (DefaultMutableTreeNode)
342					children.nextElement();
343				if(maybeReloadDirectory(child,path))
344					return true;
345			}
346		}
347
348		return false;
349	} //}}}
350
351	//{{{ loadDirectory() method
352	private void loadDirectory(DefaultMutableTreeNode node, String path,
353		boolean showLoading)
354	{
355		currentlyLoadingTreeNode = node;
356
357		if(node == rootNode)
358		{
359			parentModel.removeAllElements();
360			parentModel.addElement(new LoadingPlaceholder());
361		}
362
363		if(showLoading)
364		{
365			node.removeAllChildren();
366			node.add(new DefaultMutableTreeNode(new LoadingPlaceholder(),false));
367			model.reload(currentlyLoadingTreeNode);
368		}
369
370		tmpExpanded.clear();
371		int rowCount = tree.getRowCount();
372		for(int i = 0; i < rowCount; i++)
373		{
374			TreePath treePath = tree.getPathForRow(i);
375			if(tree.isExpanded(treePath))
376			{
377				DefaultMutableTreeNode _node = (DefaultMutableTreeNode)
378					treePath.getLastPathComponent();
379				VFS.DirectoryEntry file = ((VFS.DirectoryEntry)
380					_node.getUserObject());
381
382				tmpExpanded.put(file.path,file.path);
383			}
384		}
385
386		browser.loadDirectory(path,node == rootNode);
387	} //}}}
388
389	//{{{ showFilePopup() method
390	private void showFilePopup(VFS.DirectoryEntry file, Point point)
391	{
392		popup = new BrowserCommandsMenu(browser,file);
393		GUIUtilities.showPopupMenu(popup,tree,point.x+1,point.y+1);
394	} //}}}
395
396	//}}}
397
398	//{{{ Inner classes
399
400	//{{{ ClearTypeSelect
401	class ClearTypeSelect implements ActionListener
402	{
403		public void actionPerformed(ActionEvent evt)
404		{
405			typeSelectBuffer.setLength(0);
406			browser.filesSelected();
407		}
408	} //}}}
409
410	//{{{ ParentDirectoryRenderer class
411	class ParentDirectoryRenderer extends DefaultListCellRenderer
412	{
413		Font plainFont, boldFont;
414
415		ParentDirectoryRenderer()
416		{
417			plainFont = UIManager.getFont("Tree.font");
418			boldFont = new Font(plainFont.getName(),Font.BOLD,plainFont.getSize());
419		}
420
421		public Component getListCellRendererComponent(
422			JList list,
423			Object value,
424			int index,
425			boolean isSelected,
426			boolean cellHasFocus)
427		{
428			super.getListCellRendererComponent(list,value,index,
429				isSelected,cellHasFocus);
430
431			ParentDirectoryRenderer.this.setBorder(new EmptyBorder(
432				1,index * 17 + 1,1,1));
433
434			if(value instanceof LoadingPlaceholder)
435			{
436				ParentDirectoryRenderer.this.setFont(plainFont);
437
438				setIcon(showIcons ? FileCellRenderer.loadingIcon : null);
439				setText(jEdit.getProperty("vfs.browser.tree.loading"));
440			}
441			else
442			{
443				ParentDirectoryRenderer.this.setFont(boldFont);
444
445				setIcon(showIcons ? FileCellRenderer.openDirIcon : null);
446				setText(MiscUtilities.getFileName(value.toString()));
447			}
448
449			return this;
450		}
451	} //}}}
452
453	//{{{ MouseHandler class
454	class MouseHandler extends MouseAdapter
455	{
456		public void mouseClicked(MouseEvent evt)
457		{
458			// ignore double clicks
459			if(evt.getClickCount() == 2)
460				return;
461
462			int row = parentDirectories.locationToIndex(evt.getPoint());
463			if(row != -1)
464			{
465				Object obj = parentModel.getElementAt(row);
466				if(obj instanceof String)
467				{
468					browser.setDirectory((String)obj);
469					requestDefaultFocus();
470				}
471			}
472		}
473	} //}}}
474
475	//{{{ BrowserJTree class
476	class BrowserJTree extends JTree
477	{
478		//{{{ BrowserJTree constructor
479		BrowserJTree(TreeModel model)
480		{
481			super(model);
482			ToolTipManager.sharedInstance().registerComponent(this);
483		} //}}}
484
485		//{{{ getToolTipText() method
486		public final String getToolTipText(MouseEvent evt)
487		{
488			TreePath path = getPathForLocation(evt.getX(), evt.getY());
489			if(path != null)
490			{
491				Rectangle cellRect = getPathBounds(path);
492				if(cellRect != null && !cellRectIsVisible(cellRect))
493					return path.getLastPathComponent().toString();
494			}
495			return null;
496		} //}}}
497
498		//{{{ getToolTipLocation() method
499		public final Point getToolTipLocation(MouseEvent evt)
500		{
501			TreePath path = getPathForLocation(evt.getX(), evt.getY());
502			if(path != null)
503			{
504				Rectangle cellRect = getPathBounds(path);
505				if(cellRect != null && !cellRectIsVisible(cellRect))
506				{
507					return new Point(cellRect.x + (showIcons ? 19 : 1),
508						cellRect.y -1);
509				}
510			}
511			return null;
512		} //}}}
513
514		//{{{ processKeyEvent() method
515		protected void processKeyEvent(KeyEvent evt)
516		{
517			if(evt.getID() == KeyEvent.KEY_PRESSED)
518			{
519				switch(evt.getKeyCode())
520				{
521				case KeyEvent.VK_ENTER:
522					browser.filesActivated();
523					evt.consume();
524					break;
525				case KeyEvent.VK_LEFT:
526					String directory = browser.getDirectory();
527					browser.setDirectory(MiscUtilities
528						.getParentOfPath(directory));
529					evt.consume();
530					break;
531				}
532			}
533			else if(evt.getID() == KeyEvent.KEY_TYPED)
534			{
535				switch(evt.getKeyChar())
536				{
537				case '~':
538					browser.setDirectory(System.getProperty("user.home"));
539					break;
540				case '/':
541					browser.setDirectory("roots:");
542					break;
543				case '-':
544					View view = browser.getView();
545					Buffer buffer = view.getBuffer();
546					browser.setDirectory(MiscUtilities.getParentOfPath(
547						buffer.getPath()));
548					break;
549				default:
550					typeSelectBuffer.append(evt.getKeyChar());
551					doTypeSelect(typeSelectBuffer.toString());
552
553					timer.stop();
554					timer.setInitialDelay(750);
555					timer.setRepeats(false);
556					timer.start();
557					break;
558				}
559			}
560
561			if(!evt.isConsumed())
562				super.processKeyEvent(evt);
563		} //}}}
564
565		//{{{ processMouseEvent() method
566		protected void processMouseEvent(MouseEvent evt)
567		{
568			ToolTipManager ttm = ToolTipManager.sharedInstance();
569
570			switch(evt.getID())
571			{
572			//{{{ MOUSE_ENTERED...
573			case MouseEvent.MOUSE_ENTERED:
574				toolTipInitialDelay = ttm.getInitialDelay();
575				toolTipReshowDelay = ttm.getReshowDelay();
576				ttm.setInitialDelay(200);
577				ttm.setReshowDelay(0);
578				super.processMouseEvent(evt);
579				break; //}}}
580			//{{{ MOUSE_EXITED...
581			case MouseEvent.MOUSE_EXITED:
582				ttm.setInitialDelay(toolTipInitialDelay);
583				ttm.setReshowDelay(toolTipReshowDelay);
584				super.processMouseEvent(evt);
585				break; //}}}
586			//{{{ MOUSE_CLICKED...
587			case MouseEvent.MOUSE_CLICKED:
588				if((evt.getModifiers() & MouseEvent.BUTTON2_MASK) != 0)
589				{
590					TreePath path = getPathForLocation(evt.getX(),evt.getY());
591					if(path == null)
592					{
593						super.processMouseEvent(evt);
594						break;
595					}
596
597					if(!isPathSelected(path))
598						setSelectionPath(path);
599
600					browser.filesActivated();
601					break;
602				}
603				else if((evt.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
604				{
605					TreePath path = getPathForLocation(evt.getX(),evt.getY());
606					if(path == null)
607					{
608						super.processMouseEvent(evt);
609						break;
610					}
611
612					if(!isPathSelected(path))
613						setSelectionPath(path);
614
615					if(evt.getClickCount() == 1)
616					{
617						browser.filesSelected();
618						super.processMouseEvent(evt);
619					}
620					if(evt.getClickCount() == 2)
621					{
622						// don't pass double-clicks to tree, otherwise
623						// directory nodes will be expanded and we don't
624						// want that
625						browser.filesActivated();
626						break;
627					}
628				}
629				else if(GUIUtilities.isPopupTrigger(evt))
630					; // do nothing
631
632				super.processMouseEvent(evt);
633				break; //}}}
634			//{{{ MOUSE_PRESSED...
635			case MouseEvent.MOUSE_PRESSED:
636				if((evt.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
637				{
638					if(popup != null && popup.isVisible())
639						popup.setVisible(false);
640
641					if(evt.getClickCount() == 2)
642						break;
643				}
644				else if(GUIUtilities.isPopupTrigger(evt))
645				{
646					if(popup != null && popup.isVisible())
647					{
648						popup.setVisible(false);
649						break;
650					}
651
652					TreePath path = getPathForLocation(evt.getX(),evt.getY());
653					if(path == null)
654						showFilePopup(null,evt.getPoint());
655					else
656					{
657						setSelectionPath(path);
658						browser.filesSelected();
659
660						Object userObject = ((DefaultMutableTreeNode)path
661							.getLastPathComponent()).getUserObject();
662						if(userObject instanceof VFS.DirectoryEntry)
663						{
664							VFS.DirectoryEntry file = (VFS.DirectoryEntry)
665								userObject;
666							showFilePopup(file,evt.getPoint());
667						}
668						else
669							showFilePopup(null,evt.getPoint());
670					}
671
672					break;
673				}
674
675				super.processMouseEvent(evt);
676				break; //}}}
677			//{{{ MOUSE_RELEASED:
678			case MouseEvent.MOUSE_RELEASED:
679				if(evt.getClickCount() != 2)
680					super.processMouseEvent(evt);
681				break; //}}}
682			default:
683				super.processMouseEvent(evt);
684				break;
685			}
686		} //}}}
687
688		//{{{ Private members
689		private int toolTipInitialDelay = -1;
690		private int toolTipReshowDelay = -1;
691
692		//{{{ cellRectIsVisible() method
693		private boolean cellRectIsVisible(Rectangle cellRect)
694		{
695			Rectangle vr = BrowserJTree.this.getVisibleRect();
696			return vr.contains(cellRect.x,cellRect.y) &&
697				vr.contains(cellRect.x + cellRect.width,
698				cellRect.y + cellRect.height);
699		} //}}}
700
701		//{{{ doTypeSelect() method
702		private void doTypeSelect(String str)
703		{
704			if(getSelectionCount() == 0)
705				doTypeSelect(str,0,getRowCount());
706			else
707			{
708				int start = getMaxSelectionRow();
709				boolean retVal = doTypeSelect(str,start,getRowCount());
710
711				if(!retVal)
712				{
713					// scan from selection to end failed, so
714					// scan from start to selection
715					doTypeSelect(str,0,start);
716				}
717			}
718		} //}}}
719
720		//{{{ doTypeSelect() method
721		private boolean doTypeSelect(String str, int start, int end)
722		{
723			for(int i = start; i < end; i++)
724			{
725				DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)
726					getPathForRow(i).getLastPathComponent();
727				Object obj = treeNode.getUserObject();
728				if(obj instanceof VFS.DirectoryEntry)
729				{
730					VFS.DirectoryEntry file = (VFS.DirectoryEntry)obj;
731					if(file.name.regionMatches(true,0,str,0,str.length()))
732					{
733						clearSelection();
734						setSelectionRow(i);
735						scrollRowToVisible(i);
736						return true;
737					}
738				}
739			}
740
741			return false;
742		} //}}}
743
744		//}}}
745	} //}}}
746
747	//{{{ TreeHandler class
748	class TreeHandler implements TreeExpansionListener
749	{
750		//{{{ treeExpanded() method
751		public void treeExpanded(TreeExpansionEvent evt)
752		{
753			TreePath path = evt.getPath();
754			DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)
755				path.getLastPathComponent();
756			Object userObject = treeNode.getUserObject();
757			if(userObject instanceof VFS.DirectoryEntry)
758			{
759				loadDirectory(treeNode,((VFS.DirectoryEntry)
760					userObject).path,true);
761			}
762		} //}}}
763
764		//{{{ treeCollapsed() method
765		public void treeCollapsed(TreeExpansionEvent evt)
766		{
767			TreePath path = evt.getPath();
768			DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)
769				path.getLastPathComponent();
770			if(treeNode.getUserObject() instanceof VFS.DirectoryEntry)
771			{
772				// we add the placeholder so that the node has
773				// 1 child (otherwise the user won't be able to
774				// expand it again)
775				treeNode.removeAllChildren();
776				treeNode.add(new DefaultMutableTreeNode(new LoadingPlaceholder(),false));
777				model.reload(treeNode);
778			}
779		} //}}}
780	} //}}}
781
782	static class LoadingPlaceholder {}
783	//}}}
784}