"""
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 connectwin
import bookmark
import messages
import utils
import icons.folder
import icons.quick_connect
import icons.add_folder
import icons.add_bookmark
import icons.edit_bookmark
import wx
import os
class BookmarkWindow(wx.Frame):
"""Bookmark management window.
This window allows for the management of connection bookmarks. Bookmarks consist
of files storing connection information for later reconnecting to the same file
transfer site. Bookmarks can have hierarchical, directory structure organization,
and are managed within an actual filesystem directory structure. All paths supplied
to the bookmark manager are relative.
The bookmark window interacts with the connection window to provide an interface
for specifying connection options. This also allows a short-cut to initiating
connections directly from a bookmark."""
# Menu IDs
idCONNECT = wx.NewId()
idFOLDER_ADD = wx.NewId()
idFOLDER_RENAME = wx.NewId()
idFOLDER_DELETE = wx.NewId()
idBOOKMARK_ADD = wx.NewId()
idBOOKMARK_EDIT = wx.NewId()
idBOOKMARK_RENAME = wx.NewId()
idBOOKMARK_DELETE = wx.NewId()
# Toolbar IDs
idTOOL_CONNECT = wx.NewId()
idTOOL_ADDFOLDER = wx.NewId()
idTOOL_ADDBOOKMARK = wx.NewId()
idTOOL_EDITBOOKMARK = wx.NewId()
idTREE = wx.NewId()
# Popup Menu IDs
idPOPUP_RENAMEFOLDER = wx.NewId()
idPOPUP_DELETEFOLDER = wx.NewId()
idPOPUP_EDITBOOKMARK = wx.NewId()
idPOPUP_RENAMEBOOKMARK = wx.NewId()
idPOPUP_DELETEBOOKMARK = wx.NewId()
def __init__(self, parent):
"""Creates and displays the bookmark window.
This initializes the bookmark manager and initializes the bookmark tree navigation
view."""
wx.Frame.__init__(self, parent, -1, _("Ftpcube - Bookmarks"))
self.SetIcon(utils.getAppIcon())
self.SetSize((750, 500))
self.headers = [ _("Description"), _("Host"), _("Username"), _("Directory") ]
self.makeMenu()
self.makeToolBar()
# Create the tree and bookmark list
splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D)
self.tree = wx.TreeCtrl(splitter, self.idTREE)
self.list = wx.ListCtrl(splitter, -1, style=wx.LC_REPORT)
splitter.SplitVertically(self.tree, self.list, 200)
# Set up the tree images
self.image_list = wx.ImageList(15, 17)
self.folder_index = self.image_list.Add(icons.folder.getBitmap())
self.tree.SetImageList(self.image_list)
# Set up the list columns
for col, name in zip(range(len(self.headers)), self.headers):
self.list.InsertColumn(col, name)
self.list.SetColumnWidth(0, 200)
self.list.SetColumnWidth(1, 175)
self.list.SetColumnWidth(2, 100)
self.list.SetColumnWidth(3, 200)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(splitter, 1, wx.EXPAND)
self.SetAutoLayout(True)
self.SetSizer(sizer)
self.Center()
try:
self.bookmark = bookmark.BookmarkManager()
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, strerror)
self.root = self.tree.AddRoot(self.bookmark.BOOKMARK_DIRECTORY, self.folder_index,
self.folder_index)
self.loadBookmarkTree(self.bookmark.getBookmarkDirectory(), self.root)
# Bind tree events
self.Bind(wx.EVT_TREE_SEL_CHANGED, self.onTreeSelect, self.tree)
# Bind popup menus
self.tree.Bind(wx.EVT_RIGHT_DOWN, self.onTreeRightClick)
self.list.Bind(wx.EVT_RIGHT_DOWN, self.onListRightClick)
# Register double click to connect to bookmark
self.list.Bind(wx.EVT_LEFT_DCLICK, self.onConnect)
# Display the root directory
self.displayBookmarks()
def makeMenu(self):
"""Constructs the main menu bar."""
self.menu_bar = wx.MenuBar()
self.menu_bar.Append(self.createSiteMenu(), _("&Site"))
self.menu_bar.Append(self.createEditMenu(), _("&Edit"))
self.SetMenuBar(self.menu_bar)
def createSiteMenu(self):
"""Creates the site management menu."""
site_menu = wx.Menu()
site_menu.Append(self.idCONNECT, _("&Connect"), _("Connect to Bookmarked Site"))
self.Bind(wx.EVT_MENU, self.onConnect, id=self.idCONNECT)
return site_menu
def createEditMenu(self):
"""Creates the bookmark edit menu."""
edit_menu = wx.Menu()
edit_menu.Append(self.idFOLDER_ADD, _("Add &Folder"), _("Add a Bookmark Folder"))
edit_menu.Append(self.idFOLDER_RENAME, _("Rename Folder"),
_("Rename a Bookmark Folder"))
edit_menu.Append(self.idFOLDER_DELETE, _("Delete Folder"),
_("Delete a Bookmark Folder"))
edit_menu.AppendSeparator()
edit_menu.Append(self.idBOOKMARK_ADD, _("Add &Bookmark"), _("Add a New Bookmark"))
edit_menu.Append(self.idBOOKMARK_EDIT, _("&Edit Bookmark"),
_("Edit an Existing Bookmark"))
edit_menu.Append(self.idBOOKMARK_DELETE, _("Delete Bookmark"),
_("Delete Selected Bookmark"))
self.Bind(wx.EVT_MENU, self.onFolderAdd, id=self.idFOLDER_ADD)
self.Bind(wx.EVT_MENU, self.onFolderRename, id=self.idFOLDER_RENAME)
self.Bind(wx.EVT_MENU, self.onFolderDelete, id=self.idFOLDER_DELETE)
self.Bind(wx.EVT_MENU, self.onBookmarkAdd, id=self.idBOOKMARK_ADD)
self.Bind(wx.EVT_MENU, self.onBookmarkEdit, id=self.idBOOKMARK_EDIT)
self.Bind(wx.EVT_MENU, self.onBookmarkRename, id=self.idBOOKMARK_RENAME)
self.Bind(wx.EVT_MENU, self.onBookmarkDelete, id=self.idBOOKMARK_DELETE)
return edit_menu
def makeToolBar(self):
"""Creates the toolbar."""
toolbar = self.CreateToolBar()
toolbar.SetToolBitmapSize(wx.Size(32, 32))
bitmap = icons.quick_connect.getBitmap()
toolbar.AddSimpleTool(self.idTOOL_CONNECT, bitmap, shortHelpString=_("Connect"))
utils.addLineSeparator(toolbar, 16)
bitmap = icons.add_folder.getBitmap()
toolbar.AddSimpleTool(self.idTOOL_ADDFOLDER, bitmap, shortHelpString=_("Add Folder"))
bitmap = icons.add_bookmark.getBitmap()
toolbar.AddSimpleTool(self.idTOOL_ADDBOOKMARK, bitmap,
shortHelpString=_("Add Bookmark"))
bitmap = icons.edit_bookmark.getBitmap()
toolbar.AddSimpleTool(self.idTOOL_EDITBOOKMARK, bitmap,
shortHelpString=_("Edit Bookmark"))
self.Bind(wx.EVT_TOOL, self.onConnect, id=self.idTOOL_CONNECT)
self.Bind(wx.EVT_TOOL, self.onFolderAdd, id=self.idTOOL_ADDFOLDER)
self.Bind(wx.EVT_TOOL, self.onBookmarkAdd, id=self.idTOOL_ADDBOOKMARK)
self.Bind(wx.EVT_TOOL, self.onBookmarkEdit, id=self.idTOOL_EDITBOOKMARK)
toolbar.Realize()
def onTreeRightClick(self, event):
"""Process a right click on the bookmark tree by displaying the tree popup
menu."""
menu = self.makeTreePopupMenu()
self.tree.PopupMenu(menu, event.GetPosition())
def makeTreePopupMenu(self):
"""Creates the right-click menu for operations on the bookmark folder tree."""
menu = wx.Menu()
menu.Append(self.idPOPUP_RENAMEBOOKMARK, _("Rename Folder"))
menu.Append(self.idPOPUP_DELETEFOLDER, _("Delete Folder"))
self.Bind(wx.EVT_MENU, self.onFolderRename, id=self.idPOPUP_RENAMEBOOKMARK)
self.Bind(wx.EVT_MENU, self.onFolderDelete, id=self.idPOPUP_DELETEFOLDER)
def onListRightClick(self, event):
"""Processes a right click on a list of bookmarks."""
menu = self.makeListPopupMenu()
self.list.PopupMenu(menu, event.GetPosition())
def makeListPopupMenu(self):
"""Creates the right-click menu for a bookmark list."""
menu = wx.Menu()
menu.Append(self.idPOPUP_EDITBOOKMARK, _("Edit Bookmark"))
menu.Append(self.idPOPUP_RENAMEBOOKMARK, _("Rename Bookmark"))
menu.Append(self.idPOPUP_DELETEBOOKMARK, _("Delete Bookmark"))
self.Bind(wx.EVT_MENU, self.onBookmarkEdit, id=self.idPOPUP_EDITBOOKMARK)
self.Bind(wx.EVT_MENU, self.onBookmarkRename, id=self.idPOPUP_RENAMEBOOKMARK)
self.Bind(wx.EVT_MENU, self.onBookmarkDelete, id=self.idPOPUP_DELETEBOOKMARK)
def onConnect(self, event):
"""Processes a connect event for a bookmark.
This initiates an FTP connect to the bookmark for which the event occurred.
The bookmark is the selected bookmark within the list. This closes the
bookmark window and brings the user back to the main window, where the
connection to the bookmarked site is initiated."""
path = self.getPathToCurrentNode()
selected = utils.getSelected(self.list)
if not selected:
return
selected = selected.pop(0)
name = utils.getColumnText(self.list, selected, 0)
book_file = os.path.join(path, name)
try:
book_opts = self.bookmark.loadBookmark(book_file)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error loading bookmark: %(err)s")
%{ 'err' : strerror })
return
# Disconnect if necessary
thread_mgr = utils.getAppThreadManager()
main_window = utils.getMainWindow()
if thread_mgr.getMainThread():
if not main_window.promptDisconnect():
return
main_window.onDisconnect(event)
parent = self.GetParent()
self.Destroy()
connect = connectwin.ConnectionWindow(parent, book_opts)
ret = connect.ShowModal()
if ret == wx.ID_OK:
connect_opts = connect.getOptions()
if connect_opts['host'] and connect_opts['port']:
utils.initiateConnection(connect_opts)
else:
messages.displayErrorDialog(utils.getMainWindow(),
_("A hostname and port must be supplied"))
def onFolderAdd(self, event):
"""Processes an tree control event to add a new bookmark folder."""
selected = self.tree.GetSelections()
if not selected:
node = None
relative_path = ''
else:
node = selected.pop(0)
relative_path = self.nodeToPath(node)
folder = messages.displayInputDialog(self, _("Ftpcube - Add Bookmark Folder"),
_("Folder name:"))
if folder:
relative_path = os.path.join(relative_path, folder)
try:
self.bookmark.addBookmarkFolder(relative_path)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error creating bookmark folder: %(err)s")
%{ 'err' : strerror })
return
new_node = self.tree.AppendItem(node, folder, self.folder_index, self.folder_index)
self.tree.Expand(node)
def onFolderRename(self, event):
"""Processes a rename event for a bookmark folder within the tree control."""
selected = self.tree.GetSelections()
for node in selected:
if node == self.root:
continue
relative_path = self.nodeToPath(node)
new_folder = messages.displayInputDialog(self, _("Ftpcube - Rename Folder"),
_("Rename %(file)s to:") %{ 'file' : os.path.basename(relative_path) })
if not new_folder:
continue
new_relative_path = os.path.join(os.path.dirname(relative_path), new_folder)
try:
self.bookmark.renameBookmarkFolder(relative_path, new_relative_path)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error renaming bookmark folder: %(err)s")
%{ 'err' : strerror })
return
parent = self.tree.GetItemParent(node)
self.tree.Delete(node)
new_node = self.tree.AppendItem(parent, new_folder, self.folder_index,
self.folder_index)
self.loadBookmarkTree(new_path, new_node)
def onFolderDelete(self, event):
"""Processes a delete event for a bookmark folder within the tree control."""
selected = self.tree.GetSelections()
for node in selected:
if node == self.root:
continue
relative_path = self.nodeToPath(node)
try:
self.bookmark.removeBookmarkFolder(relative_path)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error deleting bookmark folder: %(err)s")
%{ 'err' : strerror })
return
self.tree.Delete(node)
def onBookmarkAdd(self, event):
"""Processes an event to add a new bookmark.
This brings up the connection window, whose fields can be filled up. When OK is
clicked, the options are extracted and stored into the bookmark entry."""
app_config = utils.getApplicationConfiguration()
opts = app_config.getOptions()
connect = connectwin.ConnectionWindow(self, opts, _("Ftpcube - Add Bookmark"))
ret = connect.ShowModal()
if ret == wx.ID_OK:
book_opts = connect.getOptions()
if book_opts['host']:
relative_path = os.path.join(self.getPathToCurrentNode(),
"%s-%s" %(book_opts['host'], book_opts['port']))
try:
self.bookmark.saveBookmark(relative_path, book_opts)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error adding bookmark: %(err)s")
%{ 'err' : strerror })
return
self.displayBookmarks()
def onBookmarkEdit(self, event):
"""Processes an event to edit an existing bookmark.
This brings up a connection window with the options in the bookmark prepopulated.
When the OK button is clicked, all the options are re-extracted from the connection
window and saved to the bookmark file."""
relative_path = self.getPathToCurrentNode()
selected = utils.getSelected(self.list)
if not selected:
return
selected = selected.pop(0)
name = utils.getColumnText(self.list, selected, 0)
book_file = os.path.join(relative_path, name)
try:
opts = self.bookmark.loadBookmark(book_file)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error loading bookmark: %(err)s")
%{ 'err' : strerror })
return
connect = connectwin.ConnectionWindow(self, opts, _("Ftpcube - Edit Bookmark"))
ret = connect.ShowModal()
if ret == wx.ID_OK:
book_opts = connect.getOptions()
try:
self.bookmark.saveBookmark(book_file, book_opts)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error saving bookmark: %(err)s")
%{ 'err' : strerror })
return
self.displayBookmarks()
def onBookmarkRename(self, event):
"""Processes an event to rename an existing bookmark."""
relative_path = self.getPathToCurrentNode()
selected = utils.getSelected(self.list)
if not selected:
return
selected = selected.pop(0)
name = utils.getColumnText(self.list, selected, 0)
new_name = messages.displayInputDialog(self, _("Ftpcube - Rename Bookmark"),
_("Rename %(name)s to:") %{ 'name' : name })
if not new_name:
return
try:
self.bookmark.renameBookmark(os.path.join(relative_path, name),
os.path.join(relative_path, new_name))
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error renaming bookmark: %(err)s")
%{ 'err' : strerror })
return
self.displayBookmarks()
def onBookmarkDelete(self, event):
"""Processes a bookmark entry delete event."""
relative_path = self.getPathToCurrentNode()
selected = utils.getSelected(self.list)
if not selected:
return
selected = selected.pop(0)
name = utils.getColumnText(self.list, selected, 0)
relative_path = os.path.join(relative_path, name)
try:
self.bookmark.removeBookmark(relative_path)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error deleting bookmark: %(err)s")
%{ 'err' : strerror })
return
self.displayBookmarks()
def onTreeSelect(self, event):
"""Expands an item in the tree."""
node = event.GetItem()
self.tree.Expand(node)
self.displayBookmarks()
def loadBookmarkTree(self, path, parent):
"""Performs a recursive traversal of the bookmark directory tree to load the
bookmark tree into memory."""
bookdir = self.bookmark.getBookmarkDirectory()
bookdir = os.path.join(bookdir, path)
self._loadBookmarkTree(bookdir, parent)
def _loadBookmarkTree(self, path, parent):
"""Recursive loading of the bookmark tree."""
try:
files = os.listdir(path)
except OSError, strerror:
messages.displayErrorDialog(self, _("Error reading directory %(path)s: %(err)s")
%{ 'path' : strerror, 'err' : strerror })
return
if files:
for file in files:
full_path = os.path.join(path, file)
if os.path.isdir(full_path):
node = self.tree.AppendItem(parent, file, self.folder_index,
self.folder_index)
self._loadBookmarkTree(full_path, node)
self.tree.Expand(parent)
def nodeToPath(self, node):
"""Returns the relative path from the bookmark directory to the bookmark node within
the bookmark tree."""
path = [ ]
while node != self.root:
path.insert(0, self.tree.GetItemText(node))
node = self.tree.GetItemParent(node)
path = os.sep.join(path)
return path
def getPathToCurrentNode(self):
"""Gets the relative path from the bookmark directory to the currently selected node
within the bookmark tree."""
node = self.root
selected = self.tree.GetSelections()
if selected:
node = selected.pop(0)
return self.nodeToPath(node)
def displayBookmarks(self):
"""Displays the bookmark entries within a bookmark folder in the list control.
The list widget is frozen until the update has been completed, at which point it
is rendered to the user. If an error occurs while loading a bookmark from within
the bookmark directory, then an error box is displayed to the user. This method
will attempt to load as many bookmarks as possible, despite errors."""
relative_path = self.getPathToCurrentNode()
full_path = os.path.join(self.bookmark.getBookmarkDirectory(), relative_path)
try:
files = os.listdir(full_path)
except OSError, strerror:
messages.displayErrorDialog(self, _("Error reading directory %(path)s: %(err)s")
%{ 'path' : full_path, 'err' : strerror })
return
self.list.Freeze()
self.list.DeleteAllItems()
index = 0
for file in files:
path = os.path.join(full_path, file)
if os.path.isfile(path):
try:
bookmark_entry = self.bookmark.loadBookmark(path)
except bookmark.BookmarkException, strerror:
messages.displayErrorDialog(self, _("Error loading bookmark: %(err)s")
%{ 'err' : strerror })
self.list.InsertStringItem(index, file)
host = bookmark_entry['host']
if not host:
host = ''
port = bookmark_entry['port']
if not port:
port = ''
self.list.SetStringItem(index, 1, "%s:%s" %(host, port))
username = bookmark_entry['username']
if not username:
username = ''
self.list.SetStringItem(index, 2, username)
remotedir = bookmark_entry['remotedir']
if not remotedir:
remotedir = ''
self.list.SetStringItem(index, 3, remotedir)
index = index + 1
self.list.Thaw()
|