"""
FtpCube
Copyright (C) Michael Gilfix
This file is part of FtpCube.
You should have received a file COPYING containing license terms
along with this program; if not, write to Michael Gilfix
(mgilfix@eecs.tufts.edu) for a copy.
This version of FtpCube is open source; you can redistribute it and/or
modify it under the terms listed in the file COPYING.
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.
"""
import dialog
import utils
import icons.folder
import wx
import os
class Browser(dialog.DialogWindow):
"""An abstract base class for a directory browsing windows.
A browser window provides a directory navigation tree structure for navigating
directory structures. This is a custom widget that allows for a consistent look
and feel between browsing local directories and the remote directory cache.
This window uses a tree structure to represent the directory folders, where each
directory folder can then be expanded."""
idTREE = wx.NewId()
def __init__(self, parent, title):
"""Creates a new browser instance.
This constructs the tree view, populated with directory icon."""
dialog.DialogWindow.__init__(self, parent, title)
self.SetIcon(utils.getAppIcon())
panel = self.getDialogPanel()
label = wx.StaticText(panel, -1, _("Directory:"))
self.dir_entry = wx.TextCtrl(panel, -1)
dsizer = wx.FlexGridSizer(5, 3)
dsizer.AddMany([
((20, 5)), (label, 0, wx.EXPAND), ((20, 5)),
((20, 5)), ((20, 5)), ((20, 5)),
((20, 5)), (self.dir_entry, 1, wx.EXPAND), ((20, 5)),
])
dsizer.AddGrowableCol(1)
self.tree = wx.TreeCtrl(panel, self.idTREE, style=wx.SUNKEN_BORDER | wx.TR_HAS_BUTTONS)
self.image_list = wx.ImageList(15, 17)
self.folder_index = self.image_list.Add(icons.folder.getBitmap())
self.tree.SetImageList(self.image_list)
tsizer = wx.BoxSizer(wx.HORIZONTAL)
tsizer.Add((15, 5))
tsizer.Add(self.tree, 1, wx.EXPAND)
tsizer.Add((10, 5))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add((5, 5))
sizer.Add(dsizer, 0, wx.EXPAND)
sizer.Add((5, 10))
sizer.Add(tsizer, 1, wx.EXPAND)
sizer.Add((5, 5))
panel.SetAutoLayout(True)
panel.SetSizer(sizer)
sizer.Fit(panel)
sizer.SetSizeHints(panel)
# Render the dialog buttons and set the sizers
self.renderDialog()
self.SetSize((400, 350))
self.Bind(wx.EVT_TREE_SEL_CHANGED, self.onTreeSelect, id=self.idTREE)
def getDirectory(self):
"""Returns the directory whose contents is represented by the browser."""
return self.dir_entry.GetValue()
def onTreeSelect(self, event):
"""Handles the selection of a tree element."""
raise NotImplementedError
class LocalBrowser(Browser):
"""A local directory browser.
Provides tree navigation of the local filesystem. All directory nodes are expandable.
No files are shown. The directory entry box allows for typing in the current directory
directly and is updated during browsing of the filesystem."""
def __init__(self, parent, path):
"""Creates the local browser window, starting at the specified path."""
Browser.__init__(self, parent, _("Ftpcube - Change Local Directory"))
self.root = self.tree.AddRoot(os.sep, self.folder_index, self.folder_index)
self.loadTree(path, self.root)
# Set the entry box to the starting path
self.dir_entry.SetValue(path)
def loadTree(self, path, parent):
"""Loads the tree structure up to the current path.
This loads all directories higher in the hierarchy for the current directory, as
is typically done in such browsing widgets for OS UIs. If the supplied path points
to a file, then the parent directory for the file is used."""
if not os.path.isdir(path):
path = os.path.dirname(path)
# Create a list of paths to cycle through
dirs = path.split(os.sep)
dirs[0] = os.sep
cur_path = os.sep
parent_list = [ parent ]
while dirs:
dir = dirs.pop(0)
cur_path = os.path.join(cur_path, dir)
listing = self.getDirectoryList(cur_path)
new_parent = None
for item in listing:
node = self.tree.AppendItem(parent, item, self.folder_index, self.folder_index)
if dirs and item == dirs[0]:
new_parent = node
if new_parent is not None:
parent = new_parent
parent_list.append(parent)
# Expand all parents
for p in parent_list:
self.tree.Expand(p)
def getDirectoryList(self, path):
"""Gets the listing of directories within the specified path."""
try:
listing = os.listdir(path)
except OSError, strerror:
return [ ]
listing = [ x for x in listing
if os.path.isdir(os.path.join(path, x)) ]
return listing
def onTreeSelect(self, event):
"""Handles the selection of a directory within the directory tree.
This updates the directory entry box to reflect the selected directory.
This also expands sub-nodes if need be."""
node = event.GetItem()
path = self.nodeToPath(node)
self.dir_entry.SetValue(path)
if not self.tree.GetChildrenCount(node):
self.expandNode(node, path)
self.tree.Expand(node)
def expandNode(self, parent, path):
"""Expands a directory node within the tree."""
listing = self.getDirectoryList(path)
self.tree.Freeze()
for item in listing:
node = self.tree.AppendItem(parent, item, self.folder_index, self.folder_index)
self.tree.Thaw()
def nodeToPath(self, node):
"""Returns the path to the specified node within the directory tree."""
path = [ ]
while node != self.root:
path.insert(0, self.tree.GetItemText(node))
node = self.tree.GetItemParent(node)
path = os.sep + os.sep.join(path)
return path
class RemoteBrowser(Browser):
"""Remotes site directory browser.
This widget makes use of the directory cache in order to render a tree of known remote
directories. Only the directories that are held in the cache are displayed (meaning that
the displayed tree is not complete). For directories that are in the cache, they can be
expanded and contracted like regular directories."""
def __init__(self, parent, cache, path):
"""Creates the remote directory browser window using the specified cache.
The specified path is used as the starting point in the cache hierarchy. The
directory text box is updated to the starting specified path."""
Browser.__init__(self, parent, _("Ftpcube - Change Remote Directory"))
self.cache = cache
# Load the tree contents
cache_delim = self.cache.getDelimiter()
self.root = self.tree.AddRoot(cache_delim, self.folder_index, self.folder_index)
self.loadTree(self.cache.getRoot(), self.root)
self.expandPath(path)
# Set the entry box to the starting path
self.dir_entry.SetValue(path)
def onTreeSelect(self, event):
"""Handles the selection of a directory within the directory tree.
This updates the directory entry box to reflect the selected directory.
This also expands sub-nodes if need be."""
node = event.GetItem()
path = self.nodeToPath(node)
self.dir_entry.SetValue(path)
if not self.tree.GetChildrenCount(node):
self.expandPath(path)
def loadTree(self, node, parent):
"""Recursively loads the tree from the specified cache node entry.
Parent refers to the parent widget whose sub-directories are to be populated with the
contents of the cache. It is possible that elements of the cache will have None data
elements. This can occur when the cache is explicitly cleared and the cached data is
not in synch with the current directory depth. When that happens, the path should be
used to fill in whatever blanks are known."""
if node.data is not None:
directories = [ x[0] for x in node.data
if not x[0] == '..' and x[3][0] == 'd' ]
else:
directories = self.cache.getChildren(node)
if directories is None:
return
for dir_name in directories:
insert_node = self.tree.AppendItem(parent, dir_name, self.folder_index,
self.folder_index)
child = self.cache.getChildNode(node, dir_name)
if child is not None:
self.loadTree(child, insert_node)
def nodeToPath(self, node):
"""Returns the path to the specified node within the directory tree."""
path = [ ]
while node != self.root:
path.insert(0, self.tree.GetItemText(node))
node = self.tree.GetItemParent(node)
delim = self.cache.getDelimiter()
path = self.tree.GetItemText(self.root) + delim.join(path)
return path
def expandPath(self, path):
"""Expands all directory elements in the cache on the way to the specified path."""
path = [ x for x in path.split(self.cache.getDelimiter()) if x ]
self.tree.Expand(self.root)
if not path:
return
node = self.root
for node_name in path:
# Extract the children from the tree
children = [ ]
child, cookie = self.tree.GetFirstChild(node)
while child.IsOk():
children.append(child)
child, cookie = self.tree.GetNextChild(node, cookie)
# Expand the appropriate child
child_names = [ self.tree.GetItemText(x) for x in children ]
try:
child_index = child_names.index(node_name)
except ValueError:
return None
node = children[child_index]
self.tree.Expand(node)
|