PageRenderTime 495ms CodeModel.GetById 406ms app.highlight 70ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/browser/BrowserView.java

#
Java | 756 lines | 564 code | 79 blank | 113 comment | 108 complexity | 88e73080cd0149f544dd4e40224aff26 MD5 | raw file
  1/*
  2 * BrowserView.java
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2000, 2003 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.*;
 29
 30import java.awt.event.*;
 31import java.awt.*;
 32import java.io.File;
 33import java.io.IOException;
 34import java.util.*;
 35
 36import org.gjt.sp.jedit.gui.DockableWindowManager;
 37import org.gjt.sp.jedit.io.*;
 38import org.gjt.sp.jedit.*;
 39import org.gjt.sp.util.Log;
 40import org.gjt.sp.util.ThreadUtilities;
 41//}}}
 42
 43/**
 44 * VFS browser tree view.
 45 * @author Slava Pestov
 46 * @version $Id: BrowserView.java 19692 2011-07-22 15:27:56Z kpouer $
 47 */
 48class BrowserView extends JPanel
 49{
 50	//{{{ BrowserView constructor
 51	BrowserView(final VFSBrowser browser)
 52	{
 53		this.browser = browser;
 54
 55		tmpExpanded = new HashSet<String>();
 56		DockableWindowManager dwm = jEdit.getActiveView().getDockableWindowManager();
 57		KeyListener keyListener = dwm.closeListener(VFSBrowser.NAME);
 58
 59		parentDirectories = new ParentDirectoryList();
 60		parentDirectories.addKeyListener(keyListener);
 61		parentDirectories.setName("parent");
 62		
 63		parentDirectories.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 64		parentDirectories.setCellRenderer(new ParentDirectoryRenderer());
 65		parentDirectories.setVisibleRowCount(5);
 66		parentDirectories.addMouseListener(new ParentMouseHandler());
 67
 68		final JScrollPane parentScroller = new JScrollPane(parentDirectories);
 69		parentScroller.setMinimumSize(new Dimension(0,0));
 70
 71		table = new VFSDirectoryEntryTable(this);
 72		table.addMouseListener(new TableMouseHandler());
 73		table.setName("file");
 74		JScrollPane tableScroller = new JScrollPane(table);
 75		tableScroller.setMinimumSize(new Dimension(0,0));
 76		tableScroller.getViewport().setBackground(table.getBackground());
 77		tableScroller.getViewport().addMouseListener(new TableMouseHandler());
 78		splitPane = new JSplitPane(
 79			browser.isHorizontalLayout()
 80			? JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT,
 81			jEdit.getBooleanProperty("appearance.continuousLayout"),
 82			parentScroller, tableScroller);
 83		splitPane.setOneTouchExpandable(true);
 84
 85		EventQueue.invokeLater(new Runnable()
 86		{
 87			public void run()
 88			{
 89				String prop = browser.isHorizontalLayout() ? "vfs.browser.horizontalSplitter" : "vfs.browser.splitter";
 90				int loc = jEdit.getIntegerProperty(prop,-1);
 91				if(loc == -1)
 92					loc = parentScroller.getPreferredSize().height;
 93
 94				splitPane.setDividerLocation(loc);
 95				parentDirectories.ensureIndexIsVisible(
 96					parentDirectories.getModel()
 97					.getSize());
 98			}
 99		});
100
101		if(browser.isMultipleSelectionEnabled())
102			table.getSelectionModel().setSelectionMode(
103				ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
104		else
105			table.getSelectionModel().setSelectionMode(
106				ListSelectionModel.SINGLE_SELECTION);
107
108		setLayout(new BorderLayout());
109
110		add(BorderLayout.CENTER,splitPane);
111
112		propertiesChanged();
113	} //}}}
114
115	//{{{ focusOnFileView() method
116	public void focusOnFileView()
117	{
118		table.requestFocus();
119	} //}}}
120
121	//{{{ removeNotify() method
122	@Override
123	public void removeNotify()
124	{
125		String prop = browser.isHorizontalLayout() ? "vfs.browser.horizontalSplitter" : "vfs.browser.splitter";
126		jEdit.setIntegerProperty(prop,splitPane.getDividerLocation());
127
128		super.removeNotify();
129	} //}}}
130
131	//{{{ getSelectedFiles() method
132	public VFSFile[] getSelectedFiles()
133	{
134		return table.getSelectedFiles();
135	} //}}}
136
137	//{{{ selectNone() method
138	public void selectNone()
139	{
140		table.clearSelection();
141	} //}}}
142
143	//{{{ saveExpansionState() method
144	public void saveExpansionState()
145	{
146		tmpExpanded.clear();
147		table.getExpandedDirectories(tmpExpanded);
148	} //}}}
149
150	//{{{ clearExpansionState() method
151	public void clearExpansionState()
152	{
153		tmpExpanded.clear();
154	} //}}}
155
156	//{{{ loadDirectory() method
157	public void loadDirectory(Object node, String path,
158		boolean addToHistory)
159	{
160		loadDirectory(node, path, addToHistory, null);
161	} //}}}
162
163
164	//{{{ loadDirectory() method
165	public void loadDirectory(final Object node, String path,
166		final boolean addToHistory, final Runnable delayedAWTTask)
167	{
168		path = MiscUtilities.constructPath(browser.getDirectory(),path);
169		VFS vfs = VFSManager.getVFSForPath(path);
170
171		Object session = vfs.createVFSSession(path,this);
172		if(session == null)
173			return;
174
175		if(node == null)
176		{
177			parentDirectories.setListData(new Object[] {
178				new LoadingPlaceholder() });
179		}
180
181		final Object[] loadInfo = new Object[2];
182		Runnable awtRunnable = new Runnable()
183		{
184			public void run()
185			{
186				browser.directoryLoaded(node,loadInfo,addToHistory);
187				if (delayedAWTTask != null)
188					delayedAWTTask.run();
189			}
190		};
191		ThreadUtilities.runInBackground(new ListDirectoryBrowserTask(browser,
192			session, vfs, path, loadInfo, awtRunnable));
193	} //}}}
194
195	//{{{ directoryLoaded() method
196	/**
197	 * Rebuild the parent view after a directory has been loaded.
198	 *
199	 * @param node
200	 * @param path
201	 * @param directory  
202	 */
203	public void directoryLoaded(Object node, String path, java.util.List<VFSFile> directory)
204	{
205		//{{{ If reloading root, update parent directory list
206		if(node == null)
207		{
208			DefaultListModel parentList = new DefaultListModel();
209
210			String parent = path;
211
212			for(;;)
213			{
214				VFS _vfs = VFSManager.getVFSForPath(parent);
215				VFSFile file = null;
216				if (_vfs instanceof FileVFS)
217				{
218					Object session = _vfs.createVFSSession(path, browser);
219					try
220					{
221						file = _vfs._getFile(session, parent, browser);
222						if (file != null)
223						{
224							file.setName(_vfs.getFileName(parent));
225						}
226					}
227					catch (IOException e)
228					{
229						Log.log(Log.ERROR, this, e, e);
230					}
231				}
232				if (file == null)
233				{
234					// create a DirectoryEntry manually
235					// instead of using _vfs._getFile()
236					// since so many VFS's have broken
237					// implementations of this method
238					file = new VFSFile(
239							_vfs.getFileName(parent),
240							parent,parent,
241							VFSFile.DIRECTORY,
242							0L,false);
243				}
244
245
246				/*parentList.insertElementAt(new VFSFile(
247					_vfs.getFileName(parent),
248					parent,parent,
249					VFSFile.DIRECTORY,
250					0L,false),0);*/
251				parentList.insertElementAt(file,0);
252				String newParent = _vfs.getParentOfPath(parent);
253
254				if(newParent == null ||
255					MiscUtilities.pathsEqual(parent,newParent))
256					break;
257				else
258					parent = newParent;
259			}
260
261			parentDirectories.setModel(parentList);
262			int index = parentList.getSize() - 1;
263			parentDirectories.setSelectedIndex(index);
264			parentDirectories.ensureIndexIsVisible(index);
265		} //}}}
266
267		table.setDirectory(VFSManager.getVFSForPath(path),
268			node,directory,tmpExpanded);
269	} //}}}
270
271	//{{{ updateFileView() method
272	public void updateFileView()
273	{
274		table.repaint();
275	} //}}}
276
277	//{{{ maybeReloadDirectory() method
278	public void maybeReloadDirectory(String path)
279	{
280		String browserDir = browser.getDirectory();
281		String symlinkBrowserDir;
282		if(MiscUtilities.isURL(browserDir))
283		{
284			symlinkBrowserDir = browserDir;
285		}
286		else
287		{
288			symlinkBrowserDir = MiscUtilities.resolveSymlinks(
289				browserDir);
290		}
291
292		if(MiscUtilities.pathsEqual(path,symlinkBrowserDir))
293		{
294			saveExpansionState();
295			loadDirectory(null,browserDir,false);
296		}
297
298		// because this method is called for *every* VFS update,
299		// we don't want to scan the tree all the time. So we
300		// use the following algorithm to determine if the path
301		// might be part of the tree:
302		// - if the path starts with the browser's current directory,
303		//   we do the tree scan
304		// - if the browser's directory is 'favorites:' -- we have to
305		//   do the tree scan, as every path can appear under the
306		//   favorites list
307		// - if the browser's directory is 'roots:' and path is on
308		//   the local filesystem, do a tree scan
309
310		if(!browserDir.startsWith(FavoritesVFS.PROTOCOL)
311			&& !browserDir.startsWith(FileRootsVFS.PROTOCOL)
312			&& !path.startsWith(symlinkBrowserDir))
313			return;
314
315		if(browserDir.startsWith(FileRootsVFS.PROTOCOL)
316			&& MiscUtilities.isURL(path)
317			&& !"file".equals(MiscUtilities.getProtocolOfURL(path)))
318			return;
319
320		table.maybeReloadDirectory(path);
321	} //}}}
322
323	//{{{ propertiesChanged() method
324	public void propertiesChanged()
325	{
326		showIcons = jEdit.getBooleanProperty("vfs.browser.showIcons");
327		table.propertiesChanged();
328		GUIUtilities.initContinuousLayout(splitPane);
329		splitPane.setBorder(null);
330	} //}}}
331
332	//{{{ getBrowser() method
333	/**
334	 * Returns the associated <code>VFSBrowser</code> instance.
335	 * @since jEdit 4.2pre1
336	 */
337	public VFSBrowser getBrowser()
338	{
339		return browser;
340	} //}}}
341
342	//{{{ getTable() method
343	public VFSDirectoryEntryTable getTable()
344	{
345		return table;
346	} //}}}
347
348	//{{{ getParentDirectoryList() method
349	public JList getParentDirectoryList()
350	{
351		return parentDirectories;
352	} //}}}
353
354	//{{{ Private members
355
356	//{{{ Instance variables
357	private final VFSBrowser browser;
358
359	private final JSplitPane splitPane;
360	private final JList parentDirectories;
361	private final VFSDirectoryEntryTable table;
362	private final Set<String> tmpExpanded;
363	private BrowserCommandsMenu popup;
364	private boolean showIcons;
365	//}}}
366
367	//{{{ showFilePopup() method
368	private void showFilePopup(VFSFile[] files, Component comp,
369		Point point)
370	{
371		popup = new BrowserCommandsMenu(browser,files);
372		// for the parent directory right-click; on the click we select
373		// the clicked item, but when the popup goes away we select the
374		// currently showing directory.
375		popup.addPopupMenuListener(new PopupMenuListener()
376		{
377			public void popupMenuCanceled(PopupMenuEvent e) {}
378
379			public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
380
381			public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
382			{
383				// we use SwingUtilities.invokeLater()
384				// so that the action is executed before
385				// the popup is hidden.
386				EventQueue.invokeLater(new Runnable()
387				{
388					public void run()
389					{
390						int index = parentDirectories
391							.getModel()
392							.getSize() - 1;
393						parentDirectories.setSelectedIndex(index);
394					}
395				});
396			}
397		});
398		GUIUtilities.showPopupMenu(popup,comp,point.x,point.y);
399	} //}}}
400
401	//}}}
402
403	//{{{ Inner classes
404
405	//{{{ ParentDirectoryRenderer class
406	class ParentDirectoryRenderer extends DefaultListCellRenderer
407	{
408		private Font plainFont;
409		private final Font boldFont;
410
411		ParentDirectoryRenderer()
412		{
413			plainFont = UIManager.getFont("Tree.font");
414			if(plainFont == null)
415				plainFont = jEdit.getFontProperty("metal.secondary.font");
416			boldFont = new Font(plainFont.getName(),Font.BOLD,plainFont.getSize());
417		}
418
419		@Override
420		public Component getListCellRendererComponent(
421			JList list,
422			Object value,
423			int index,
424			boolean isSelected,
425			boolean cellHasFocus)
426		{
427			super.getListCellRendererComponent(list,value,index,
428				isSelected,cellHasFocus);
429
430			ParentDirectoryRenderer.this.setBorder(new EmptyBorder(
431				1,index * 5 + 1,1,1));
432
433			if(value instanceof LoadingPlaceholder)
434			{
435				ParentDirectoryRenderer.this.setFont(plainFont);
436
437				setIcon(showIcons ? FileCellRenderer.loadingIcon : null);
438				setText(jEdit.getProperty("vfs.browser.tree.loading"));
439			}
440			else if(value instanceof VFSFile)
441			{
442				VFSFile dirEntry = (VFSFile)value;
443				ParentDirectoryRenderer.this.setFont(boldFont);
444
445				setIcon(showIcons ? FileCellRenderer.getIconForFile(dirEntry,true)
446					: null);
447				setText(dirEntry.getName());
448			}
449			else if(value == null)
450				setText("VFS does not follow VFS API");
451
452			return this;
453		}
454	} //}}}
455
456	//{{{ ParentMouseHandler class
457	private class ParentMouseHandler extends MouseAdapter
458	{
459		@Override
460		public void mousePressed(MouseEvent evt)
461		{
462			int row = parentDirectories.locationToIndex(evt.getPoint());
463			if(row != -1)
464			{
465				Object obj = parentDirectories.getModel()
466					.getElementAt(row);
467				if(obj instanceof VFSFile)
468				{
469					VFSFile dirEntry = (VFSFile)obj;
470					if(GUIUtilities.isPopupTrigger(evt))
471					{
472						if(popup != null && popup.isVisible())
473						{
474							popup.setVisible(false);
475							popup = null;
476						}
477						else
478						{
479							parentDirectories.setSelectedIndex(row);
480							showFilePopup(new VFSFile[] {
481								dirEntry },parentDirectories,
482								evt.getPoint());
483						}
484					}
485				}
486			}
487		}
488
489		@Override
490		public void mouseReleased(MouseEvent evt)
491		{
492			if(evt.getClickCount() % 2 != 0 &&
493				!GUIUtilities.isMiddleButton(evt.getModifiers()))
494				return;
495
496			int row = parentDirectories.locationToIndex(evt.getPoint());
497			if(row != -1)
498			{
499				Object obj = parentDirectories.getModel()
500					.getElementAt(row);
501				if(obj instanceof VFSFile)
502				{
503					VFSFile dirEntry = (VFSFile)obj;
504					if(!GUIUtilities.isPopupTrigger(evt))
505					{
506						browser.setDirectory(dirEntry.getPath());
507						if(browser.getMode() == VFSBrowser.BROWSER)
508						focusOnFileView();
509					}
510				}
511			}
512		}
513	} //}}}
514
515	//{{{ TableMouseHandler class
516	private class TableMouseHandler extends MouseAdapter
517	{
518		//{{{ mouseClicked() method
519		@Override
520		public void mouseClicked(MouseEvent evt)
521		{
522			Point p = evt.getPoint();
523			int row = table.rowAtPoint(p);
524			int column = table.columnAtPoint(p);
525			if(row == -1)
526				return;
527			if(column == 0)
528			{
529				VFSDirectoryEntryTableModel.Entry entry
530					= (VFSDirectoryEntryTableModel.Entry)
531					table.getModel().getValueAt(row,0);
532				if(FileCellRenderer.ExpansionToggleBorder
533					.isExpansionToggle(entry.level,p.x))
534				{
535					return;
536				}
537			}
538
539			if((evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0
540				&& evt.getClickCount() % 2 == 0)
541			{
542				browser.filesActivated(evt.isShiftDown()
543					? VFSBrowser.M_OPEN_NEW_VIEW
544					: VFSBrowser.M_OPEN,true);
545			}
546			else if(GUIUtilities.isMiddleButton(evt.getModifiers()))
547			{
548				if(evt.isShiftDown())
549					table.getSelectionModel().addSelectionInterval(row,row);
550				else
551					table.getSelectionModel().setSelectionInterval(row,row);
552				browser.filesActivated(evt.isShiftDown()
553					? VFSBrowser.M_OPEN_NEW_VIEW
554					: VFSBrowser.M_OPEN,true);
555			}
556		} //}}}
557
558		//{{{ mousePressed() method
559		@Override
560		public void mousePressed(MouseEvent evt)
561		{
562			Point p = evt.getPoint();
563			if(evt.getSource() != table)
564			{
565				p.x -= table.getX();
566				p.y -= table.getY();
567			}
568
569			int row = table.rowAtPoint(p);
570			int column = table.columnAtPoint(p);
571			if(column == 0 && row != -1)
572			{
573				VFSDirectoryEntryTableModel.Entry entry
574					= (VFSDirectoryEntryTableModel.Entry)
575					table.getModel().getValueAt(row,0);
576				if(FileCellRenderer.ExpansionToggleBorder
577					.isExpansionToggle(entry.level,p.x))
578				{
579					table.toggleExpanded(row);
580					return;
581				}
582			}
583
584			if(GUIUtilities.isMiddleButton(evt.getModifiers()))
585			{
586				if(row == -1)
587					/* nothing */;
588				else if(evt.isShiftDown())
589					table.getSelectionModel().addSelectionInterval(row,row);
590				else
591					table.getSelectionModel().setSelectionInterval(row,row);
592			}
593			else if(GUIUtilities.isPopupTrigger(evt))
594			{
595				if(popup != null && popup.isVisible())
596				{
597					popup.setVisible(false);
598					popup = null;
599					return;
600				}
601
602				if(row == -1)
603					showFilePopup(null,table,evt.getPoint());
604				else
605				{
606					if(!table.getSelectionModel().isSelectedIndex(row))
607						table.getSelectionModel().setSelectionInterval(row,row);
608					showFilePopup(getSelectedFiles(),table,evt.getPoint());
609				}
610			}
611		} //}}}
612
613		//{{{ mouseReleased() method
614		@Override
615		public void mouseReleased(MouseEvent evt)
616		{
617			if(!GUIUtilities.isPopupTrigger(evt)
618				&& table.getSelectedRow() != -1)
619			{
620				browser.filesSelected();
621			}
622		} //}}}
623	} //}}}
624
625	private static class LoadingPlaceholder {}
626	//}}}
627	
628	class ParentDirectoryList extends JList
629	{
630
631		public String getPath(int row)
632		{
633			Collection<String> components = new LinkedList<String>();
634			for (int i=1; i<=row; ++i)
635				components.add(getModel().getElementAt(i).toString());
636			return getModel().getElementAt(0) + TextUtilities.join(components, File.separator);
637		}
638
639		@Override
640		protected void processKeyEvent(KeyEvent evt)
641		{
642			if (evt.getID() == KeyEvent.KEY_PRESSED)
643			{
644				ActionContext ac = VFSBrowser.getActionContext();
645				int row = parentDirectories.getSelectedIndex();
646				switch(evt.getKeyCode())
647				{
648				case KeyEvent.VK_DOWN:
649					evt.consume();			
650					if (row < parentDirectories.getSize().height-1) 
651						parentDirectories.setSelectedIndex(++row);
652					break;
653				case KeyEvent.VK_LEFT:
654					if ((evt.getModifiers() & InputEvent.ALT_MASK)>0)
655					{
656						evt.consume();
657						browser.previousDirectory();
658					}
659					else super.processEvent(evt);
660					break;
661				case KeyEvent.VK_RIGHT:
662					if ((evt.getModifiers() & InputEvent.ALT_MASK)>0)
663					{
664						evt.consume();
665						browser.nextDirectory();
666					}
667					else super.processEvent(evt);
668					break;
669				case KeyEvent.VK_TAB:
670					evt.consume();
671					if ((evt.getModifiers() & InputEvent.SHIFT_MASK) > 0)
672						browser.focusOnDefaultComponent();
673					else
674						table.requestFocus();
675					break;
676				case KeyEvent.VK_UP :
677					evt.consume();
678					if (row > 0)
679					{
680						parentDirectories.setSelectedIndex(--row);
681					}
682					break;
683				case KeyEvent.VK_BACK_SPACE:
684					evt.consume();
685					EditAction up = ac.getAction("vfs.browser.up");
686					ac.invokeAction(evt, up);
687					break;
688				case KeyEvent.VK_F5: 
689					evt.consume();
690					EditAction reload = ac.getAction("vfs.browser.reload");
691					ac.invokeAction(evt, reload);
692					break;
693				case KeyEvent.VK_ENTER: 
694					evt.consume();
695					String path = getPath(row);
696					getBrowser().setDirectory(path);
697					table.requestFocus();
698					break;
699/* These actions don't work because they look at the EntryTable for the current selected
700 * 	item. We need actions that look at the parentDirectoryList item instead.
701 * 					
702				case KeyEvent.VK_DELETE:
703					evt.consume();
704					ea = ac.getAction("vfs.browser.delete");
705					ac.invokeAction(evt, ea);
706					break; 
707				case KeyEvent.CTRL_MASK | KeyEvent.VK_N:  
708					evt.consume();
709					ea = ac.getAction("vfs.browser.new-file");
710					ac.invokeAction(evt, ea);
711					break;
712				case KeyEvent.VK_INSERT:
713					evt.consume();
714					ea = ac.getAction("vfs.browser.new-directory");
715					ac.invokeAction(evt, ea);
716					break; */					
717				}
718			}
719			else if(evt.getID() == KeyEvent.KEY_TYPED)
720			{
721				if(evt.isControlDown() || evt.isAltDown()
722					|| evt.isMetaDown())
723				{
724					evt.consume();
725					return;
726				}
727				switch(evt.getKeyChar())
728				{
729				case '~':
730					evt.consume();
731					if(browser.getMode() == VFSBrowser.BROWSER)
732						browser.setDirectory(System.getProperty(
733							"user.home"));
734					break;
735				case '/':
736					evt.consume();
737					if(browser.getMode() == VFSBrowser.BROWSER)
738						browser.rootDirectory();
739					break;
740				case '-':
741					evt.consume();
742					if(browser.getMode() == VFSBrowser.BROWSER)
743					{
744						browser.setDirectory(
745							browser.getView().getBuffer()
746							.getDirectory());
747					}
748					break;
749				}
750			}
751			if (!evt.isConsumed())
752				super.processKeyEvent(evt);
753		}	
754	}
755	
756}