/*
* BrowserView.java
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2000, 2003 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.browser;
//{{{ Imports
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.io.IOException;
import java.util.*;
import org.gjt.sp.jedit.gui.DockableWindowManager;
import org.gjt.sp.jedit.io.*;
import org.gjt.sp.jedit.*;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.ThreadUtilities;
//}}}
/**
* VFS browser tree view.
* @author Slava Pestov
* @version $Id: BrowserView.java 22045 2012-08-23 08:44:05Z kpouer $
*/
class BrowserView extends JPanel
{
//{{{ BrowserView constructor
BrowserView(final VFSBrowser browser)
{
this.browser = browser;
tmpExpanded = new HashSet<String>();
DockableWindowManager dwm = jEdit.getActiveView().getDockableWindowManager();
KeyListener keyListener = dwm.closeListener(VFSBrowser.NAME);
parentDirectories = new ParentDirectoryList();
parentDirectories.addKeyListener(keyListener);
parentDirectories.setName("parent");
parentDirectories.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
parentDirectories.setCellRenderer(new ParentDirectoryRenderer());
parentDirectories.setVisibleRowCount(5);
parentDirectories.addMouseListener(new ParentMouseHandler());
final JScrollPane parentScroller = new JScrollPane(parentDirectories);
parentScroller.setMinimumSize(new Dimension(0,0));
table = new VFSDirectoryEntryTable(this);
table.addMouseListener(new TableMouseHandler());
table.addKeyListener(new TableKeyListener());
table.setName("file");
JScrollPane tableScroller = new JScrollPane(table);
tableScroller.setMinimumSize(new Dimension(0,0));
tableScroller.getViewport().setBackground(table.getBackground());
tableScroller.getViewport().addMouseListener(new TableMouseHandler());
splitPane = new JSplitPane(
browser.isHorizontalLayout()
? JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT,
parentScroller, tableScroller);
splitPane.setOneTouchExpandable(true);
EventQueue.invokeLater(new Runnable()
{
public void run()
{
String prop = browser.isHorizontalLayout() ? "vfs.browser.horizontalSplitter" : "vfs.browser.splitter";
int loc = jEdit.getIntegerProperty(prop,-1);
if(loc == -1)
loc = parentScroller.getPreferredSize().height;
splitPane.setDividerLocation(loc);
parentDirectories.ensureIndexIsVisible(
parentDirectories.getModel()
.getSize());
}
});
if(browser.isMultipleSelectionEnabled())
table.getSelectionModel().setSelectionMode(
ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
else
table.getSelectionModel().setSelectionMode(
ListSelectionModel.SINGLE_SELECTION);
setLayout(new BorderLayout());
add(BorderLayout.CENTER,splitPane);
propertiesChanged();
} //}}}
//{{{ focusOnFileView() method
public void focusOnFileView()
{
table.requestFocus();
} //}}}
//{{{ removeNotify() method
@Override
public void removeNotify()
{
String prop = browser.isHorizontalLayout() ? "vfs.browser.horizontalSplitter" : "vfs.browser.splitter";
jEdit.setIntegerProperty(prop,splitPane.getDividerLocation());
super.removeNotify();
} //}}}
//{{{ getSelectedFiles() method
public VFSFile[] getSelectedFiles()
{
return table.getSelectedFiles();
} //}}}
//{{{ selectNone() method
public void selectNone()
{
table.clearSelection();
} //}}}
//{{{ saveExpansionState() method
public void saveExpansionState()
{
tmpExpanded.clear();
table.getExpandedDirectories(tmpExpanded);
} //}}}
//{{{ clearExpansionState() method
public void clearExpansionState()
{
tmpExpanded.clear();
} //}}}
//{{{ loadDirectory() method
public void loadDirectory(Object node, String path,
boolean addToHistory)
{
loadDirectory(node, path, addToHistory, null);
} //}}}
//{{{ loadDirectory() method
public void loadDirectory(final Object node, String path,
final boolean addToHistory, final Runnable delayedAWTTask)
{
path = MiscUtilities.constructPath(browser.getDirectory(),path);
VFS vfs = VFSManager.getVFSForPath(path);
Object session = vfs.createVFSSession(path,this);
if(session == null)
{
if (delayedAWTTask != null)
ThreadUtilities.runInDispatchThread(delayedAWTTask);
return;
}
if(node == null)
{
parentDirectories.setListData(new Object[] {
new LoadingPlaceholder() });
}
final Object[] loadInfo = new Object[2];
Runnable awtRunnable = new Runnable()
{
public void run()
{
browser.directoryLoaded(node,loadInfo,addToHistory);
if (delayedAWTTask != null)
delayedAWTTask.run();
}
};
ThreadUtilities.runInBackground(new ListDirectoryBrowserTask(browser,
session, vfs, path, loadInfo, awtRunnable));
} //}}}
//{{{ directoryLoaded() method
/**
* Rebuild the parent view after a directory has been loaded.
*
* @param node
* @param path
* @param directory
*/
public void directoryLoaded(Object node, String path, java.util.List<VFSFile> directory)
{
//{{{ If reloading root, update parent directory list
if(node == null)
{
DefaultListModel parentList = new DefaultListModel();
String parent = path;
for(;;)
{
VFS _vfs = VFSManager.getVFSForPath(parent);
VFSFile file = null;
if (_vfs instanceof FileVFS)
{
Object session = _vfs.createVFSSession(path, browser);
try
{
file = _vfs._getFile(session, parent, browser);
if (file != null)
{
file.setName(_vfs.getFileName(parent));
}
}
catch (IOException e)
{
Log.log(Log.ERROR, this, e, e);
}
}
if (file == null)
{
// create a DirectoryEntry manually
// instead of using _vfs._getFile()
// since so many VFS's have broken
// implementations of this method
file = new VFSFile(
_vfs.getFileName(parent),
parent,parent,
VFSFile.DIRECTORY,
0L,false);
}
/*parentList.insertElementAt(new VFSFile(
_vfs.getFileName(parent),
parent,parent,
VFSFile.DIRECTORY,
0L,false),0);*/
parentList.insertElementAt(file,0);
String newParent = _vfs.getParentOfPath(parent);
if(newParent == null ||
MiscUtilities.pathsEqual(parent,newParent))
break;
else
parent = newParent;
}
parentDirectories.setModel(parentList);
int index = parentList.getSize() - 1;
parentDirectories.setSelectedIndex(index);
parentDirectories.ensureIndexIsVisible(index);
} //}}}
table.setDirectory(VFSManager.getVFSForPath(path),
node,directory,tmpExpanded);
} //}}}
//{{{ updateFileView() method
public void updateFileView()
{
table.repaint();
} //}}}
//{{{ maybeReloadDirectory() method
public void maybeReloadDirectory(String path)
{
String browserDir = browser.getDirectory();
String symlinkBrowserDir;
if(MiscUtilities.isURL(browserDir))
{
symlinkBrowserDir = browserDir;
}
else
{
symlinkBrowserDir = MiscUtilities.resolveSymlinks(
browserDir);
}
if(MiscUtilities.pathsEqual(path,symlinkBrowserDir))
{
saveExpansionState();
loadDirectory(null,browserDir,false);
}
// because this method is called for *every* VFS update,
// we don't want to scan the tree all the time. So we
// use the following algorithm to determine if the path
// might be part of the tree:
// - if the path starts with the browser's current directory,
// we do the tree scan
// - if the browser's directory is 'favorites:' -- we have to
// do the tree scan, as every path can appear under the
// favorites list
// - if the browser's directory is 'roots:' and path is on
// the local filesystem, do a tree scan
if(!browserDir.startsWith(FavoritesVFS.PROTOCOL)
&& !browserDir.startsWith(FileRootsVFS.PROTOCOL)
&& !path.startsWith(symlinkBrowserDir))
return;
if(browserDir.startsWith(FileRootsVFS.PROTOCOL)
&& MiscUtilities.isURL(path)
&& !"file".equals(MiscUtilities.getProtocolOfURL(path)))
return;
table.maybeReloadDirectory(path);
} //}}}
//{{{ propertiesChanged() method
public void propertiesChanged()
{
showIcons = jEdit.getBooleanProperty("vfs.browser.showIcons");
table.propertiesChanged();
splitPane.setBorder(null);
} //}}}
//{{{ getBrowser() method
/**
* Returns the associated <code>VFSBrowser</code> instance.
* @since jEdit 4.2pre1
*/
public VFSBrowser getBrowser()
{
return browser;
} //}}}
//{{{ getTable() method
public VFSDirectoryEntryTable getTable()
{
return table;
} //}}}
//{{{ getParentDirectoryList() method
public JList getParentDirectoryList()
{
return parentDirectories;
} //}}}
//{{{ Private members
//{{{ Instance variables
private final VFSBrowser browser;
private final JSplitPane splitPane;
private final JList parentDirectories;
private final VFSDirectoryEntryTable table;
private final Set<String> tmpExpanded;
private BrowserCommandsMenu popup;
private boolean showIcons;
//}}}
//{{{ showFilePopup() method
private void showFilePopup(VFSFile[] files, Component comp,
Point point)
{
popup = new BrowserCommandsMenu(browser,files);
// for the parent directory right-click; on the click we select
// the clicked item, but when the popup goes away we select the
// currently showing directory.
popup.addPopupMenuListener(new PopupMenuListener()
{
public void popupMenuCanceled(PopupMenuEvent e) {}
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
{
// we use SwingUtilities.invokeLater()
// so that the action is executed before
// the popup is hidden.
EventQueue.invokeLater(new Runnable()
{
public void run()
{
int index = parentDirectories
.getModel()
.getSize() - 1;
parentDirectories.setSelectedIndex(index);
}
});
}
});
GUIUtilities.showPopupMenu(popup,comp,point.x,point.y);
} //}}}
//}}}
//{{{ Inner classes
//{{{ ParentDirectoryRenderer class
class ParentDirectoryRenderer extends DefaultListCellRenderer
{
private Font plainFont;
private final Font boldFont;
ParentDirectoryRenderer()
{
plainFont = UIManager.getFont("Tree.font");
if(plainFont == null)
plainFont = jEdit.getFontProperty("metal.secondary.font");
boldFont = new Font(plainFont.getName(),Font.BOLD,plainFont.getSize());
}
@Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
super.getListCellRendererComponent(list,value,index,
isSelected,cellHasFocus);
ParentDirectoryRenderer.this.setBorder(new EmptyBorder(
1,index * 5 + 1,1,1));
if(value instanceof LoadingPlaceholder)
{
ParentDirectoryRenderer.this.setFont(plainFont);
setIcon(showIcons ? FileCellRenderer.loadingIcon : null);
setText(jEdit.getProperty("vfs.browser.tree.loading"));
}
else if(value instanceof VFSFile)
{
VFSFile dirEntry = (VFSFile)value;
ParentDirectoryRenderer.this.setFont(boldFont);
setIcon(showIcons ? FileCellRenderer.getIconForFile(dirEntry,true)
: null);
setText(dirEntry.getName());
}
else if(value == null)
setText("VFS does not follow VFS API");
return this;
}
} //}}}
//{{{ ParentMouseHandler class
private class ParentMouseHandler extends MouseAdapter
{
@Override
public void mousePressed(MouseEvent evt)
{
int row = parentDirectories.locationToIndex(evt.getPoint());
if(row != -1)
{
Object obj = parentDirectories.getModel()
.getElementAt(row);
if(obj instanceof VFSFile)
{
VFSFile dirEntry = (VFSFile)obj;
if(GUIUtilities.isPopupTrigger(evt))
{
if(popup != null && popup.isVisible())
{
popup.setVisible(false);
popup = null;
}
else
{
parentDirectories.setSelectedIndex(row);
showFilePopup(new VFSFile[] {
dirEntry },parentDirectories,
evt.getPoint());
}
}
}
}
}
@Override
public void mouseReleased(MouseEvent evt)
{
if(evt.getClickCount() % 2 != 0 &&
!GUIUtilities.isMiddleButton(evt.getModifiers()))
return;
int row = parentDirectories.locationToIndex(evt.getPoint());
if(row != -1)
{
Object obj = parentDirectories.getModel()
.getElementAt(row);
if(obj instanceof VFSFile)
{
VFSFile dirEntry = (VFSFile)obj;
if(!GUIUtilities.isPopupTrigger(evt))
{
browser.setDirectory(dirEntry.getPath());
if(browser.getMode() == VFSBrowser.BROWSER)
focusOnFileView();
}
}
}
}
} //}}}
//{{{ TableKeyListener class
private class TableKeyListener extends KeyAdapter
{
@Override
public void keyPressed(KeyEvent e)
{
switch(e.getKeyCode())
{
case KeyEvent.VK_CONTEXT_MENU:
if(popup != null && popup.isVisible())
{
popup.setVisible(false);
popup = null;
return;
}
int row = table.getSelectedRow();
Point pos = new Point(0, row * table.getRowHeight());
if(row == -1)
showFilePopup(null,table,pos);
else
{
if(!table.getSelectionModel().isSelectedIndex(row))
table.getSelectionModel().setSelectionInterval(row,row);
showFilePopup(getSelectedFiles(),table,pos);
}
break;
}
}
} //}}}
//{{{ TableMouseHandler class
private class TableMouseHandler extends MouseAdapter
{
//{{{ mouseClicked() method
@Override
public void mouseClicked(MouseEvent evt)
{
Point p = evt.getPoint();
int row = table.rowAtPoint(p);
int column = table.columnAtPoint(p);
if(row == -1)
return;
if(column == 0)
{
VFSDirectoryEntryTableModel.Entry entry
= (VFSDirectoryEntryTableModel.Entry)
table.getModel().getValueAt(row,0);
if(FileCellRenderer.ExpansionToggleBorder
.isExpansionToggle(entry.level,p.x))
{
return;
}
}
if((evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0
&& evt.getClickCount() % 2 == 0)
{
browser.filesActivated(evt.isShiftDown()
? VFSBrowser.M_OPEN_NEW_VIEW
: VFSBrowser.M_OPEN,true);
}
else if(GUIUtilities.isMiddleButton(evt.getModifiers()))
{
if(evt.isShiftDown())
table.getSelectionModel().addSelectionInterval(row,row);
else
table.getSelectionModel().setSelectionInterval(row,row);
browser.filesActivated(evt.isShiftDown()
? VFSBrowser.M_OPEN_NEW_VIEW
: VFSBrowser.M_OPEN,true);
}
} //}}}
//{{{ mousePressed() method
@Override
public void mousePressed(MouseEvent evt)
{
Point p = evt.getPoint();
if(evt.getSource() != table)
{
p.x -= table.getX();
p.y -= table.getY();
}
int row = table.rowAtPoint(p);
int column = table.columnAtPoint(p);
if(column == 0 && row != -1)
{
VFSDirectoryEntryTableModel.Entry entry
= (VFSDirectoryEntryTableModel.Entry)
table.getModel().getValueAt(row,0);
if(FileCellRenderer.ExpansionToggleBorder
.isExpansionToggle(entry.level,p.x))
{
table.toggleExpanded(row);
return;
}
}
if(GUIUtilities.isMiddleButton(evt.getModifiers()))
{
if(row == -1)
/* nothing */;
else if(evt.isShiftDown())
table.getSelectionModel().addSelectionInterval(row,row);
else
table.getSelectionModel().setSelectionInterval(row,row);
}
else if(GUIUtilities.isPopupTrigger(evt))
{
if(popup != null && popup.isVisible())
{
popup.setVisible(false);
popup = null;
return;
}
if(row == -1)
showFilePopup(null,table,evt.getPoint());
else
{
if(!table.getSelectionModel().isSelectedIndex(row))
table.getSelectionModel().setSelectionInterval(row,row);
showFilePopup(getSelectedFiles(),table,evt.getPoint());
}
}
} //}}}
//{{{ mouseReleased() method
@Override
public void mouseReleased(MouseEvent evt)
{
if(!GUIUtilities.isPopupTrigger(evt)
&& table.getSelectedRow() != -1)
{
browser.filesSelected();
}
} //}}}
} //}}}
private static class LoadingPlaceholder {}
//}}}
class ParentDirectoryList extends JList
{
@Override
protected void processKeyEvent(KeyEvent evt)
{
if (evt.getID() == KeyEvent.KEY_PRESSED)
{
ActionContext ac = VFSBrowser.getActionContext();
int row = parentDirectories.getSelectedIndex();
switch(evt.getKeyCode())
{
case KeyEvent.VK_DOWN:
evt.consume();
if (row < parentDirectories.getSize().height-1)
parentDirectories.setSelectedIndex(++row);
break;
case KeyEvent.VK_LEFT:
if ((evt.getModifiers() & InputEvent.ALT_MASK)>0)
{
evt.consume();
browser.previousDirectory();
}
else super.processEvent(evt);
break;
case KeyEvent.VK_RIGHT:
if ((evt.getModifiers() & InputEvent.ALT_MASK)>0)
{
evt.consume();
browser.nextDirectory();
}
else super.processEvent(evt);
break;
case KeyEvent.VK_TAB:
evt.consume();
if ((evt.getModifiers() & InputEvent.SHIFT_MASK) > 0)
browser.focusOnDefaultComponent();
else
table.requestFocus();
break;
case KeyEvent.VK_UP :
evt.consume();
if (row > 0)
{
parentDirectories.setSelectedIndex(--row);
}
break;
case KeyEvent.VK_BACK_SPACE:
evt.consume();
EditAction up = ac.getAction("vfs.browser.up");
ac.invokeAction(evt, up);
break;
case KeyEvent.VK_F5:
evt.consume();
EditAction reload = ac.getAction("vfs.browser.reload");
ac.invokeAction(evt, reload);
break;
case KeyEvent.VK_ENTER:
evt.consume();
if(row != -1)
{
// basically the same handling as in ParentMouseHandler#mouseReleased
Object obj = parentDirectories.getModel()
.getElementAt(row);
if(obj instanceof VFSFile)
{
VFSFile dirEntry = (VFSFile)obj;
browser.setDirectory(dirEntry.getPath());
if(browser.getMode() == VFSBrowser.BROWSER)
focusOnFileView();
}
}
break;
/* These actions don't work because they look at the EntryTable for the current selected
* item. We need actions that look at the parentDirectoryList item instead.
*
case KeyEvent.VK_DELETE:
evt.consume();
ea = ac.getAction("vfs.browser.delete");
ac.invokeAction(evt, ea);
break;
case KeyEvent.CTRL_MASK | KeyEvent.VK_N:
evt.consume();
ea = ac.getAction("vfs.browser.new-file");
ac.invokeAction(evt, ea);
break;
case KeyEvent.VK_INSERT:
evt.consume();
ea = ac.getAction("vfs.browser.new-directory");
ac.invokeAction(evt, ea);
break; */
}
}
else if(evt.getID() == KeyEvent.KEY_TYPED)
{
if(evt.isControlDown() || evt.isAltDown()
|| evt.isMetaDown())
{
evt.consume();
return;
}
switch(evt.getKeyChar())
{
case '~':
evt.consume();
if(browser.getMode() == VFSBrowser.BROWSER)
browser.setDirectory(System.getProperty(
"user.home"));
break;
case '/':
evt.consume();
if(browser.getMode() == VFSBrowser.BROWSER)
browser.rootDirectory();
break;
case '-':
evt.consume();
if(browser.getMode() == VFSBrowser.BROWSER)
{
browser.setDirectory(
browser.getView().getBuffer()
.getDirectory());
}
break;
}
}
if (!evt.isConsumed())
super.processKeyEvent(evt);
}
}
}