#----------------------------------------------------------------------
# Name: Explorer.py
# Purpose: Controls to explore and initialise different data sources
#
# Author: Riaan Booysen
#
# Created: 2000/11/02
# RCS-ID: $Id: Explorer.py,v 1.42 2007/07/02 15:01:10 riaan Exp $
# Copyright: (c) 1999 - 2007 Riaan Booysen
# Licence: GPL
#----------------------------------------------------------------------
print 'importing Explorers'
from os import path
import os, sys
import time, glob, fnmatch
from types import ClassType
import wx
import Preferences, Utils
from Preferences import IS
from Utils import _
from Models import EditorHelper
import ExplorerNodes
from ExplorerNodes import TransportError,TransportLoadError,TransportSaveError
from ExplorerNodes import TransportCategoryError
#---Explorer utility functions--------------------------------------------------
def makeCategoryEx(protocol, name='', struct=None):
""" """
for cat in ExplorerNodes.all_transports.entries:
if hasattr(cat, 'itemProtocol') and cat.itemProtocol == protocol:
catName = cat.newItem()
if name:
cat.renameItem(catName, name)
else:
name = catName
if struct:
cat.entries[name].clear()
cat.entries[name].update(struct)
cat.updateConfig()
return name
raise TransportCategoryError, _('No category found for protocol %s')%protocol
def openEx(filename, transports=None):
""" Returns a transport node for the given uri """
prot, category, respath, filename = splitURI(filename)
if transports is None and ExplorerNodes.all_transports:
transports = ExplorerNodes.all_transports
return getTransport(prot, category, respath, transports)
def listdirEx(filepath, extfilter = ''):
""" Returns a list of transport nodes for given folderish filepath """
return [n.treename for n in openEx(filepath).openList()
if not extfilter or \
os.path.splitext(n.treename)[1].lower() == extfilter]
# XXX Handle compound URIs by splitting on the first 2 :// and calling
# XXX splitURI again recursively ??
def splitURI(filename):
protsplit = filename.split('://')
# check FS (No prot defaults to 'file')
if len(protsplit) == 1:
return 'file', '', filename, 'file://'+filename
else:
itemLen = len(protsplit)
if ExplorerNodes.uriSplitReg.has_key( (protsplit[0], itemLen) ):
return ExplorerNodes.uriSplitReg[(protsplit[0], itemLen)]\
(*([filename]+protsplit[1:]))
else:
prot, filepath = protsplit
idx = filepath.find('/')
if idx == -1:
raise TransportCategoryError(_('Category not found'), filepath)
else:
category, respath = filepath[:idx], filepath[idx+1:]
return prot, category, respath, filename
def getTransport(prot, category, respath, transports):
if ExplorerNodes.transportFindReg.has_key(prot):
return ExplorerNodes.transportFindReg[prot](category, respath, transports)
elif category:
return findCatExplorerNode(prot, category, respath, transports)
else:
raise TransportError(_('Unhandled transport'), (prot, category, respath))
def findCatExplorerNode(prot, category, respath, transports):
for cat in transports.entries:
if hasattr(cat, 'itemProtocol') and cat.itemProtocol == prot:
itms = cat.openList()
for itm in itms:
if itm.name == category or itm.treename == category:
# connect if not a stateless protocol
#if itm.connection:
# itm.openList()
return itm.getNodeFromPath(respath)
raise TransportError(_('Catalog transport could not be found: %s || %s')%(category, respath))
#-------------------------------------------------------------------------------
(wxID_PFE, wxID_PFT, wxID_PFL) = Utils.wxNewIds(3)
class BaseExplorerTree(wx.TreeCtrl):
def __init__(self, parent, images):
wx.TreeCtrl.__init__(self, parent, wxID_PFT, style=wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN)#|wxNO_BORDER)
self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnOpen, id=wxID_PFT)
self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnOpened, id=wxID_PFT)
self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnClose, id=wxID_PFT)
self.SetImageList(images)
self.itemCache = None
self.buildTree()
def buildTree(self):
pass
def destroy(self):
pass
def openDefaultNodes(self):
rootItem = self.GetRootItem()
self.SetItemHasChildren(rootItem, True)
self.Expand(rootItem)
return rootItem
def getChildren(self):
children = []
selection = self.GetSelection()
child, cookie = self.GetFirstChild(selection)
while child.IsOk():
children.append(child)
child, cookie = self.GetNextChild(selection, cookie)
return children
def getChildrenNames(self):
return [self.GetItemText(id) for id in self.getChildren()]
def getChildNamed(self, node, name):
if not node.IsOk():
return None
child, cookie = self.GetFirstChild(node)
while child.IsOk() and self.GetItemText(child) != name:
child, cookie = self.GetNextChild(node, cookie)
return child
def OnOpened(self, event):
pass
def OnOpen(self, event):
item = event.GetItem()
if self.IsExpanded(item): return
data = self.GetPyData(item)
hasFolders = True
if data:
wx.BeginBusyCursor()
try:
self.DeleteChildren(item)
if self.itemCache:
lst = self.itemCache[:]
else:
lst = data.openList()
hasFolders = False
for itm in lst:
if itm.isFolderish():
hasFolders = True
new = self.AppendItem(item, itm.treename or itm.name,
itm.imgIdx, -1, wx.TreeItemData(itm))
self.SetItemHasChildren(new, True)
if itm.bold:
self.SetItemBold(new, True)
if itm.refTree:
itm.treeitem = new
if itm.colour:
self.SetItemTextColour(new, itm.colour)
finally:
wx.EndBusyCursor()
self.SetItemHasChildren(item, True)#hasFolders)
def OnClose(self, event):
item = event.GetItem()
data = self.GetPyData(item)
data.closeList()
def importTransport(moduleName):
try:
__import__(moduleName, globals())
except ImportError, error:
if Preferences.pluginErrorHandling == 'raise':
raise
wx.LogWarning(_('%s not installed: %s') %(moduleName, str(error)))
ExplorerNodes.failedModules[moduleName] = str(error)
return True
else:
ExplorerNodes.installedModules.append(moduleName)
return False
class ExplorerStore:
def __init__(self, editor):
self._ref_all_transp = False
conf = Utils.createAndReadConfig('Explorer')
self.importExplorers(conf)
# Create clipboards for all registered nodes
self.clipboards = {'global': ExplorerNodes.GlobalClipper()}
for Clss, info in ExplorerNodes.explorerNodeReg.items():
Clip = info['clipboard']
if type(Clip) is ClassType:
self.clipboards[Clss.protocol] = Clip(self.clipboards['global'])
# Root node and transports
self.boaRoot = ExplorerNodes.RootNode('Boa Constructor')
self.openEditorFiles = \
ExplorerNodes.nodeRegByProt['boa.open-models'](editor, self.boaRoot)
self.transports = ExplorerNodes.ContainerNode('Transport', EditorHelper.imgFolder)
self.transports.entriesByProt = {}
self.transports.bold = True
if ExplorerNodes.all_transports is None:
ExplorerNodes.all_transports = self.transports
self._ref_all_transp = True
self.recentFiles = ExplorerNodes.MRUCatNode(self.clipboards, conf, None,
self.transports, self)
self.bookmarks = ExplorerNodes.BookmarksCatNode(self.clipboards, conf,
None, self.transports, self)
self.pluginNodes = [
ExplorerNodes.nodeRegByProt[prot](self.clipboards['file'], None, self.bookmarks)
for prot in ExplorerNodes.explorerRootNodesReg]
self.preferences = \
ExplorerNodes.nodeRegByProt['boa.prefs.group'](self.boaRoot)
assert self.clipboards.has_key('file'), _('File system transport must be loaded')
# root level of the tree
self.boaRoot.entries = [self.openEditorFiles, self.recentFiles, self.bookmarks,
self.transports] + self.pluginNodes + [self.preferences]
# Populate transports with registered node categories
# Protocol also has to be defined in the explorer section of the config
transport_order = eval(conf.get('explorer', 'transportstree'), {})
for name in transport_order:
for Clss in ExplorerNodes.explorerNodeReg.keys():
if Clss.protocol == name:
Cat = ExplorerNodes.explorerNodeReg[Clss]['category']
if not Cat: break
Clip = ExplorerNodes.explorerNodeReg[Clss]['clipboard']
if type(Clip) == type(''):
clip = self.clipboards[Clip]
elif self.clipboards.has_key(Clss.protocol):
clip = self.clipboards[Clss.protocol]
else:
clip = None
confSect, confItem = ExplorerNodes.explorerNodeReg[Clss]['confdef']
if conf.has_option(confSect, confItem):
try:
cat = Cat(clip, conf, None, self.bookmarks)
self.transports.entries.append(cat)
self.transports.entriesByProt[Cat.itemProtocol] = cat
except Exception, error:
wx.LogWarning(_('Transport category %s not added: %s')\
%(Cat.defName, str(error)))
break
def importExplorers(self, conf):
""" Import names defined in the config files to register them """
installTransports = ['Explorers.PrefsExplorer', 'Explorers.EditorExplorer'] +\
eval(conf.get('explorer', 'installedtransports'), {})
warned = False
for moduleName in installTransports:
warned = warned | importTransport(moduleName)
if warned:
wx.LogWarning(_('One or more transports could not be loaded, if the problem '
'is not rectifiable,\nconsider removing the transport under '
'Preferences->Plug-ins->Transports. Click "Details"'))
def initInstalledControllers(self, editor, list):
""" Creates controllers for built-in, plugged-in and installed nodes
in the order specified by installedModules """
controllers = {}
links = []
for instMod in ['Explorers.ExplorerNodes', 'PaletteMapping'] + \
ExplorerNodes.installedModules:
for Clss, info in ExplorerNodes.explorerNodeReg.items():
if Clss.__module__ == instMod and info['controller']:
Ctrlr = info['controller']
if type(Ctrlr) == type(''):
links.append((Clss.protocol, Ctrlr))
else:
controllers[Clss.protocol] = Ctrlr(editor, list,
editor.inspector, controllers)
for protocol, link in links:
controllers[protocol] = controllers[link]
return controllers
def destroy(self):
if self._ref_all_transp:
ExplorerNodes.all_transports = None
self.transports = None
self.clipboards = None
self.bookmarks.cleanup()
self.bookmarks = None
self.boaRoot = None
class ExplorerTree(BaseExplorerTree):
def __init__(self, parent, images, store):
self.store = store
BaseExplorerTree.__init__(self, parent, images)
def buildTree(self):
rootItem = self.AddRoot('', EditorHelper.imgBoaLogo, -1,
wx.TreeItemData(self.store.boaRoot))
def destroy(self):
self.defaultBookmarkItem = None
def openDefaultNodes(self):
rootItem = BaseExplorerTree.openDefaultNodes(self)
bktn = self.getChildNamed(rootItem, 'Bookmarks')
self.Expand(bktn)
trtn = self.getChildNamed(rootItem, 'Transport')
self.Expand(trtn)
self.defaultBookmarkItem = self.getChildNamed(bktn,
self.store.bookmarks.getDefault())
class BaseExplorerList(wx.ListCtrl, Utils.ListCtrlSelectionManagerMix):
def __init__(self, parent, filepath, pos=wx.DefaultPosition,
size=wx.DefaultSize, updateNotify=None, style=0, menuFunc=None):
wx.ListCtrl.__init__(self, parent, wxID_PFL, pos=pos, size=size,
style=wx.LC_LIST | wx.LC_EDIT_LABELS | wx.CLIP_CHILDREN | style)
Utils.ListCtrlSelectionManagerMix.__init__(self)
self.filepath = filepath
self.idxOffset = 0
self.updateNotify = updateNotify
self.node = None
self.menuFunc = menuFunc
self.selected = -1
self.items = None
self.currImages = None
self._destr = False
self.setLocalFilter()
def destroy(self):
if self._destr: return
self.DeleteAllItems()
if self.node:
self.node.destroy()
self.currImages = None
self.items = None
self.node = None
self._destr = True
# def EditLabel(self, index):
# wx.Yield()
#
# try: return wx.ListCtrl.EditLabel(self, index)
# except AttributeError: return 0
def getPopupMenu(self):
return self.menuFunc()
#---Selection-------------------------------------------------------------------
def selectItemNamed(self, name):
for idx in range(self.GetItemCount()):
item = self.GetItem(idx)
if item.GetText() == name:
item.SetState(wx.LIST_STATE_FOCUSED | wx.LIST_STATE_SELECTED)
self.SetItem(item)
self.EnsureVisible(idx)
self.selected = idx
return item
def selectItemByIdx(self, idx):
item = self.GetItem(idx)
item.SetState(wx.LIST_STATE_FOCUSED | wx.LIST_STATE_SELECTED)
self.SetItem(item)
self.selected = idx
return item
def hasItemNamed(self, name):
for idx in range(self.GetItemCount()):
if self.GetItemText(idx) == name:
return True
return False
def getAllNames(self):
names = []
for idx in range(self.GetItemCount()):
name = self.GetItemText(idx)
if name != '..':
names.append(name)
return names
def getSelection(self):
# XXX Fix, this can return IndexErrors !!!
if self.selected >= self.idxOffset:
return self.items[self.selected-self.idxOffset]
else:
return None
def getMultiSelection(self):
""" Returns list of indexes that map back to node list """
res = []
# if deselection occured, ignore item state and return []
if self.selected == -1:
return res
for idx in range(self.idxOffset, self.GetItemCount()):
if self.GetItemState(idx, wx.LIST_STATE_SELECTED):
res.append(idx-self.idxOffset)
return res
def setLocalFilter(self, filter='*'):
if glob.has_magic(filter):
self.localFilter = filter
else:
self.localFilter = '*'
def refreshCurrent(self):
self.refreshItems(self.currImages, self.node)
def refreshItems(self, images, explNode):
""" Display ExplorerNode items """
# Try to get the file listing before changing anything.
self.selected = -1
if self.node:
self.node.destroy()
self.node = explNode
self.SetImageList(images, wx.IMAGE_LIST_SMALL)
self.currImages = images
# Setup a blank list
self.DeleteAllItems()
self.items = []
self.InsertImageStringItem(self.GetItemCount(), '..', explNode.upImgIdx)
wx.BeginBusyCursor()
try: items = explNode.openList()
finally: wx.EndBusyCursor()
# Build a filtered, sorted list
orderedList = []
for itm in items:
name = itm.treename or itm.name
if itm.isFolderish() or fnmatch.fnmatch(name, self.localFilter):
if Preferences.exCaseInsensitiveSorting:
sortName = name.lower()
else:
sortName = name
orderedList.append( (not itm.isFolderish(), sortName, name, itm) )
if not explNode.vetoSort :
orderedList.sort()
# Populate the ctrl
self.idxOffset = 1
for dummy, dummy, name, itm in orderedList:
self.items.append(itm)
self.InsertImageStringItem(self.GetItemCount(), name, itm.imgIdx)
self.filepath = explNode.resourcepath
if self.updateNotify:
self.updateNotify()
def openNodeInEditor(self, item, editor, recentFiles):
if self.node.parentOpensChildren:
res = self.node.open(item, editor)
else:
res = item.open(editor)
if res and len(res) == 2:
mod, ctrlr = res
if recentFiles and mod:
recentFiles.add(mod.filename)
def OnItemSelect(self, event):
self.selected = event.m_itemIndex
event.Skip()
def OnItemDeselect(self, event):
if not self.GetSelectedItemCount():
self.selected = -1
event.Skip()
class ExplorerList(BaseExplorerList):
pass
class BaseExplorerSplitter(wx.SplitterWindow):
def __init__(self, parent, modimages, editor, store,
XList=ExplorerList, XTree=ExplorerTree):
wx.SplitterWindow.__init__(self, parent, wxID_PFE,
style=wx.CLIP_CHILDREN | wx.SP_LIVE_UPDATE)# | wxNO_3D | wxSP_3D)
self.editor = editor
self.store = store
self.list, self.listContainer = self.createList(XList, '')
self.modimages = modimages
self.list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnOpen, id=self.list.GetId())
self.list.Bind(wx.EVT_LEFT_DOWN, self.OnListClick)
self.list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelect, id=wxID_PFL)
self.list.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselect, id=wxID_PFL)
self.tree, self.treeContainer = self.createTree(XTree, modimages)
self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnSelecting, id=wxID_PFT)
self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelect, id=wxID_PFT)
self.controllers = self.initInstalledControllers()
self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginLabelEdit, id=wxID_PFL)
self.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnEndLabelEdit, id=wxID_PFL)
self.SplitVertically(self.treeContainer, self.listContainer,
Preferences.exDefaultTreeWidth)
self.SetMinimumPaneSize(self.GetSashSize())
self.list.SetFocus()
def createTree(self, XTree, modimages):
tree = XTree(self, modimages, self.store)
return tree, tree
def createList(self, XList, name):
list = XList(self, name, updateNotify=self.OnUpdateNotify,
menuFunc=self.getMenu)
return list, list
def addTools(self, toolbar):
if self.list.node and self.controllers.has_key(self.list.node.protocol):
prot = self.list.node.protocol
tbMenus = []
for menuLst in self.controllers[prot].toolbarMenus:
tbMenus.extend(list(menuLst))
for wID, name, meth, bmp in tbMenus:
if name == '-' and not bmp:
toolbar.AddSeparator()
elif bmp != '-':
if name[0] == '+':
# XXX Add toggle button
name = name [1:]
Utils.AddToolButtonBmpObject(self.editor, toolbar,
IS.load(bmp), name, meth)
def getMenu(self):
if self.list.node and self.controllers.has_key(self.list.node.protocol):
return self.controllers[self.list.node.protocol].menu
else:
return None
def destroy(self):
if not self.editor:
return
self.modimages = None
self.list.Enable(False)
self.list.destroy()
self.tree.Enable(False)
self.tree.destroy()
unqDct = {}
for contr in self.controllers.values():
unqDct[contr] = None
for contr in unqDct.keys():
contr.destroy()
self.controllers = None
self.list = None
self.editor = None
def editorUpdateNotify(self):
if self.list.node and self.controllers.has_key(self.list.node.protocol):
self.controllers[self.list.node.protocol].editorUpdateNotify()
def selectTreeItem(self, item):
data = self.tree.GetPyData(item)
title = self.tree.GetItemText(item)
if data:
imgs = data.images
if not imgs: imgs = self.modimages
self.list.refreshItems(imgs, data)
title = data.getTitle()
self.editor.SetTitle('%s - Explorer - %s' % (self.editor.editorTitle, title))
def initInstalledControllers(self):
return self.store.initInstalledControllers(self.editor, self.list)
def OnUpdateNotify(self):
tItm = self.tree.GetSelection()
# XXX this should be smarter, only refresh on folderish name change
# XXX add or remove
if not self.selecting and self.tree.IsExpanded(tItm):
self.tree.Collapse(tItm)
self.tree.Expand(tItm)
# XXX this is ugly :(
# only update toolbar when the explorer is active
if self.editor.tabs.GetSelection() == 1:
self.editor.setupToolBar(1)
def OnSelecting(self, event):
self.selecting = True
def OnSelect(self, event):
# Event is triggered twice, work around with flag
if self.selecting:
item = event.GetItem()
try:
self.selectTreeItem(item)
finally:
self.selecting = False
event.Skip()
def OnOpen(self, event):
tree, list = self.tree, self.list
if list.selected != -1:
name = list.GetItemText(self.list.selected)
nd = list.node
if name == '..':
if not nd.openParent(self.editor):
treeItem = tree.GetItemParent(tree.GetSelection())
if treeItem.IsOk():
tree.SelectItem(treeItem)
else:
## if event and event.AltDown() and \
## self.controllers.has_key(list.node.protocol):
## ctrlr = self.controllers[list.node.protocol]
## if hasattr(ctrlr, 'OnInspectItem'):
## event.Skip()
## ctrlr.OnInspectItem(None)
## return
item = list.items[list.selected-1]
if item.isFolderish():
tItm = tree.GetSelection()
if not tree.IsExpanded(tItm):
tree.itemCache = self.list.items
try: tree.Expand(tItm)
finally: tree.itemCache = None
chid = tree.getChildNamed(tree.GetSelection(), name)
tree.SelectItem(chid)
else:
list.openNodeInEditor(item, self.editor, self.store.recentFiles)
def OnKeyPressed(self, event):
key = event.GetKeyCode()
if key == 13:
self.OnOpen(event)
else:
event.Skip()
def OnListClick(self, event):
palette = self.editor.palette
if palette.componentSB.selection and self.list.node and \
self.list.node.canAdd(palette.componentSB.prevPage.name):
name, desc, Compn = palette.componentSB.selection
newName = self.list.node.newItem(name, Compn)
try:
self.list.refreshCurrent()
self.list.selectItemNamed(newName)
finally:
palette.componentSB.selectNone()
else:
event.Skip()
def OnBeginLabelEdit(self, event):
self.oldLabelVal = event.GetText()
if self.list.node:
self.list.node.notifyBeginLabelEdit(event)
if event.IsAllowed():
event.Skip()
def OnEndLabelEdit(self, event):
newText = event.GetText()
renameNode = self.list.getSelection()
assert renameNode, _('There must be a selection to rename')
oldURI = renameNode.getURI()
if newText != self.oldLabelVal:
event.Skip()
try:
self.list.node.renameItem(self.oldLabelVal, newText)
except:
wx.CallAfter(self.list.refreshCurrent)
raise
self.list.refreshCurrent()
# XXX Renames on files with unsaved changes should have opt out
# XXX Maybe load renamedNode from openEx
self.list.selectItemNamed(newText)
renamedNode = self.list.getSelection()
# XXX Type changes and unknown types are not handled!
if renamedNode:
self.editor.explorerRenameNotify(oldURI, renamedNode)
else:
event.Skip()
def OnItemSelect(self, event):
self.list.OnItemSelect(event)
if self.list.node:
sel = self.list.getSelection()
if not sel: sel = self.list.node
self.editor.statusBar.setHint(sel.getDescription())
def OnItemDeselect(self, event):
self.list.OnItemDeselect(event)
if self.list.node:
self.editor.statusBar.setHint(self.list.node.getDescription())
def OnSplitterDoubleClick(self, event):
pass
class ExplorerSplitter(BaseExplorerSplitter): pass
|