#---------------------------------------------------------------------- # Name: EditorViews.py # Purpose: Base view classes that are the visual plugins for models # # Author: Riaan Booysen # # Created: 1999 # RCS-ID: $Id: EditorViews.py,v 1.42 2005/05/26 08:03:04 riaan Exp $ # Copyright: (c) 1999 - 2005 Riaan Booysen # Licence: GPL #---------------------------------------------------------------------- print 'importing Views' import os, sys import wx import wx.html import Preferences, Utils from Preferences import IS, staticInfoPrefs, keyDefs import Search from Models import EditorHelper # XXX Python specific views should probably move out to PythonViews wxwHeaderTemplate ='''
Classes
%(ClassList)s
Functions
%(FunctionList)s
Modules
%(ModuleList)s
Classes
%(ClassList)s
Functions
%(FunctionList)s
Derived from
%(ClassSuper)s
Methods
%(MethodList)s
%(MethodDetails)s
%(MethodSynopsis)s
''' wxwFunctionTemplate = '''
%(FunctionSynopsis)s
'''
wxwFooterTemplate = ''
class EditorView:
plugins = ()
def __init__(self, model, actions=(), dclickActionIdx=-1,
editorIsWindow=True, overrideDClick=False):
self.active = False
self.model = model
try:self.editorDisconnect = self.model.editor.Disconnect
except: pass
self.modified = False
if editorIsWindow:
self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
self.Bind(wx.EVT_RIGHT_UP, self.OnRightClick)
dt = Utils.BoaFileDropTarget(model.editor)
self.SetDropTarget(dt)
self.popx = self.popy = 0
self.canExplore = False
actions = list(actions)
self.plugins = [Plugin(model, self, actions) for Plugin in self.plugins]
self.actions = tuple(actions)
self.defaultActionIdx = dclickActionIdx
self.buildMethodIds()
self.buildMenuDefn()
# Connect default action of the view to doubleclick on view
if not overrideDClick and dclickActionIdx < len(actions) and dclickActionIdx > -1:
self.Bind(wx.EVT_LEFT_DCLICK, actions[dclickActionIdx][1])
def destroy(self):
self.disconnectEvts()
self.model = None
self.methodsIds = None
del self.actions
#---Action management-----------------------------------------------------------
def buildMethodIds(self):
self.methodsIds = []
for name, meth, bmp, accl in self.actions:
if name != '-':
self.methodsIds.append( (wx.NewId(), meth) )
def buildMenuDefn(self):
self.accelLst = []
self.menuDefn = []
mIds = self.methodsIds[:]
mIds.reverse()
# Build Edit/popup menu and accelerator list
for name, meth, bmp, accl in self.actions:
if name == '-':
wId = -1
else:
wId, _m = mIds.pop()
if name[0] == '+':
canCheck = True
name = name[1:]
else:
canCheck = False
if accl:
code = keyDefs[accl]
else:
code = ()
#name = name + (keyDefs[accl][2] and ' \t'+keyDefs[accl][2] or '')
self.menuDefn.append( (wId, name, code, bmp, canCheck) )
if accl:
self.accelLst.append( (code[0], code[1], wId) )
def generateMenu(self):
menu = wx.Menu()
for wId, name, code, bmp, canCheck in self.menuDefn:
if name != '-':
Utils.appendMenuItem(menu, wId, name, code, bmp)
else:
menu.AppendSeparator()
return menu
def addViewMenus(self):
self.buildMenuDefn()
return self.generateMenu(), self.accelLst
def connectEvts(self):
for wId, meth in self.methodsIds:
self.Bind(wx.EVT_MENU, meth, id=wId)
self.model.editor.Bind(wx.EVT_MENU, meth, id=wId)
def disconnectEvts(self):
if self.model:
for wId, meth in self.methodsIds:
self.Disconnect(wId)
self.editorDisconnect(wId)
def addViewTools(self, toolbar):
addedSep = False
for name, meth, bmp, accls in self.actions:
if name == '-' and not bmp:
toolbar.AddSeparator()
addedSep = True
elif bmp != '-':
if name[0] == '+':
# XXX Add toggle button
name = name [1:]
if not addedSep:
# this is the separator between File and Edit
toolbar.AddSeparator()
addedSep = True
Utils.AddToolButtonBmpObject(self.model.editor, toolbar,
IS.load(bmp), name, meth)
#---Page management-------------------------------------------------------------
docked = True
def addToNotebook(self, notebook, viewName='', panel=None):
self.notebook = notebook
if not viewName: viewName = self.viewName
if panel:
notebook.AddPage(panel, viewName)
else:
notebook.AddPage(self, viewName)
#wxYield()
self.pageIdx = notebook.GetPageCount() -1
self.modified = False
self.readOnly = False
def deleteFromNotebook(self, focusView, tabName):
# set selection to source view
# check that not already destroyed
if hasattr(self, 'model'):
if self.modified:
#if wx.MessageBox('View modified, apply changes?',
# 'Close View',
# wx.OK | wxCANCEL | wx.ICON_EXCLAMATION) == wx.YES:
self.refreshModel()
self.model.reorderFollowingViewIdxs(self.pageIdx)
# XXX If the last view closes should the model close ??
if self.model.views.has_key(focusView):
self.model.views[focusView].focus()
del self.model.views[tabName]
self.destroy()
self.notebook.DeletePage(self.pageIdx)
#---Editor status updating------------------------------------------------------
def updatePageName(self):
if hasattr(self, 'notebook'):
currName = self.notebook.GetPageText(self.pageIdx)
if self.isModified(): newName = '~%s~' % self.viewName
else: newName = self.viewName
if currName != newName:
if newName == self.viewName:
if self.model.viewsModified.count(self.viewName):
self.model.viewsModified.remove(self.viewName)
else:
if not self.model.viewsModified.count(self.viewName):
self.model.viewsModified.append(self.viewName)
self.notebook.SetPageText(self.pageIdx, newName)
self.updateEditor()
def updateEditor(self):
self.model.editor.updateModuleState(self.model)
def updateViewState(self):
self.updatePageName()
#---Standard interface----------------------------------------------------------
def activate(self):
self.active = True
if self.modified: self.refresh()
def deactivate(self):
self.active = False
def update(self):
self.modified = True
if self.active:
self.refresh()
def refresh(self):
self.refreshCtrl()
self.modified = False
def refreshModel(self):
""" Override this to apply changes in your view to the model """
self.model.update()
self.model.notify()
def focus(self, refresh=True):
if hasattr(self, 'notebook'):
self.notebook.SetSelection(self.pageIdx)
if refresh:
## self.notebook.Refresh()
self.SetFocus()
def saveNotification(self):
pass
def close(self):
self.destroy()
def isModified(self):
return self.modified
def explore(self):
""" Return items for Explorer """
return []
def gotoBrowseMarker(self, marker):
""" Called by the browse history stack, children should override to
participate in the HistoryBrowser """
self.focus()
def OnRightDown(self, event):
self.popx = event.GetX()
self.popy = event.GetY()
def OnRightClick(self, event):
menu = self.generateMenu()
event.GetEventObject().PopupMenuXY(menu, event.GetX(), event.GetY())
menu.Destroy()
class TestView(wx.TextCtrl, EditorView):
viewName = 'Test'
def __init__(self, parent, model):
wx.TextCtrl.__init__(self, parent, -1, '',
style=wx.TE_MULTILINE | wx.TE_RICH | wx.HSCROLL)
EditorView.__init__(self, model, (), 5)
self.active = True
def refreshCtrl(self):
self.SetValue('')
class HTMLView(wx.html.HtmlWindow, EditorView):
prevBmp = 'Images/Shared/Previous.png'
nextBmp = 'Images/Shared/Next.png'
viewName = 'HTML'
def __init__(self, parent, model, actions = ()):
wx.html.HtmlWindow.__init__(self, parent, style=wx.SUNKEN_BORDER)
EditorView.__init__(self, model, (('Back', self.OnPrev, self.prevBmp, ''),
('Forward', self.OnNext, self.nextBmp, '') )+ actions, -1)
self.SetRelatedFrame(model.editor, 'Editor')
self.SetRelatedStatusBar(1)
model.editor.statusBar.setHint('')
self.title = 'HTML'
self.data = ''
self.active = True
def generatePage(self):
return ''
def refreshCtrl(self):
self.data = self.generatePage()
self.SetPage(self.data)
def OnPrev(self, event):
self.HistoryBack()
def OnNext(self, event):
self.HistoryForward()
class HTMLFileView(HTMLView):
viewName = 'View'
def generatePage(self):
return self.model.data
# XXX Add structured text/wiki option for doc strings
# XXX Option to only list documented methods
class HTMLDocView(HTMLView):
viewName = 'Documentation'
printBmp = 'Images/Shared/Print.png'
def __init__(self, parent, model, actions = ()):
HTMLView.__init__(self, parent, model, (
('-', None, '', ''),
('Save HTML', self.OnSaveHTML, '-', ''),
('Print', self.OnPrintHTML, self.printBmp, ''), )+ actions)
self.title = 'Boa docs'
self.printer = wx.html.HtmlEasyPrinting()
def generatePage(self):
page = wxwHeaderTemplate % {'Title': self.title}
page = self.genCustomPage(page) + wxwFooterTemplate
return page
def genCustomPage(self, page):
""" Override to make the page a little more interesting """
return page
def OnSaveHTML(self, event):
from FileDlg import wxFileDialog
dlg = wx.FileDialog(self, 'Save as...', '.', '', '*.html',
wx.SAVE | wx.OVERWRITE_PROMPT)
try:
if dlg.ShowModal() == wx.ID_OK:
from Explorers.Explorer import openEx
trpt = openEx(dlg.GetPath())
trpt.save(trpt.currentFilename(), self.data)
finally:
dlg.Destroy()
def OnPrintHTML(self, event):
self.printer.PrintText(self.generatePage())
class ModuleDocView(HTMLDocView):
def genCustomPage(self, page):
return self.genModuleSect(page)
def genModuleSect(self, page):
classList, classNames = self.genClassListSect()
funcList, funcNames = self.genFuncListSect()
module = self.model.getModule()
modBody = wxwModuleTemplate % { \
'ModuleSynopsis': module.getModuleDoc(),
'Module': self.model.moduleName,
'ClassList': classList,
'FunctionList': funcList,
}
return self.genFunctionsSect(\
self.genClassesSect(page + modBody, classNames), funcNames)
def genListSect(self, names):
lst = []
for name in names:
lst.append('%s' %(name, name))
return '
'.join(lst)
def genClassListSect(self):
classNames = self.model.getModule().class_order
return self.genListSect(classNames), classNames
def genFuncListSect(self):
funcNames = self.model.getModule().function_order
return self.genListSect(funcNames), funcNames
def genClassesSect(self, page, classNames):
clsBody = ''
classes = []
module = self.model.getModule()
for aclass in classNames:
supers = []
for super in module.classes[aclass].super:
try:
supers.append('%s'%(super.name, super.name))
except:
supers.append(super)
if len(supers) > 0:
supers = ', '.join(supers)
else:
supers = ''
methlist, meths = self.genMethodSect(aclass)
clsBody = wxwClassTemplate % { \
'Class': aclass,
'ClassSuper': supers,
'ClassSynopsis': module.getClassDoc(aclass),
'MethodList': methlist,
'MethodDetails': meths,
}
classes.append(clsBody)
return page + ' '.join(classes)
def genMethodSect(self, aclass):
methlist = []
meths = []
module = self.model.getModule()
methods = module.classes[aclass].methods.keys()
methods.sort()
for ameth in methods:
methlist.append('%(Method)s
' % {\
'Class': aclass,
'Method': ameth})
methBody = wxwMethodTemplate % { \
'Class': aclass,
'Method': ameth,
'MethodSynopsis': module.getClassMethDoc(aclass, ameth),
'Params': module.classes[aclass].methods[ameth].signature,
}
meths.append(methBody)
return ' '.join(methlist), ' '.join(meths)
def genFunctionsSect(self, page, funcNames):
funcBody = ''
functions = []
module = self.model.getModule()
for func in funcNames:
funcBody = wxwFunctionTemplate % { \
'Function': func,
'Params': module.functions[func].signature,
'FunctionSynopsis': module.getFunctionDoc(func),
}
functions.append(funcBody)
return page + ' '.join(functions)
# XXX For editing views there should be a prompt before closing.
class CloseableViewMix:
""" Defines a closing action for views like results.
Deletes page named tabName
"""
closeViewBmp = 'Images/Editor/CloseView.png'
def __init__(self, hint = 'results'):
self.closingActionItems = ( ('Close '+ hint, self.OnClose,
self.closeViewBmp, 'CloseView'), )
def OnClose(self, event):
del self.closingActionItems
self.deleteFromNotebook('Source', self.tabName)
class CyclopsView(HTMLView, CloseableViewMix):
viewName = 'Cyclops report'
def __init__(self, parent, model):
CloseableViewMix.__init__(self)
HTMLView.__init__(self, parent, model, ( ('-', -1, '', ''), ) +
self.closingActionItems)
def OnLinkClicked(self, linkinfo):
""" classlink, attriblink """
url = linkinfo.GetHref()
if url[0] == '#':
self.base_OnLinkClicked(linkinfo)
else:
jumpType, jumpPath = url.split('://')
segs = jumpPath.split('.')
if jumpType == 'classlink':
mod, clss = segs[-2:]
if len(segs) > 2:
pack = segs[:-2]
else:
pack = []
elif jumpType == 'attrlink':
mod, clss, attr = segs[-3:]
if len(segs) > 3:
pack = segs[:-3]
else:
pack = []
for dirname in sys.path:
fullname = os.path.abspath(os.path.join(dirname, mod+'.py'))
if os.path.exists(fullname):
found = fullname
break
else:
pckPth = '/'.join(pack)
fullname = os.path.abspath(os.path.join(dirname, pckPth, mod+'.py'))
if os.path.exists(fullname):
found = fullname
break
else: return
model, controller = self.model.editor.openOrGotoModule(fullname)
module = model.getModule()
if jumpType == 'classlink':
lineno = module.classes[clss].block.start
elif jumpType == 'attrlink':
if module.classes[clss].attributes.has_key(attr):
lineno = module.classes[clss].attributes[attr][0].start
elif module.classes[clss].methods.has_key(attr):
lineno = module.classes[clss].methods[attr].start
else:
lineno = module.classes[clss].block.start
mod, clss, attr = segs[-3:]
if len(segs) > 3:
pack = segs[:-3]
else:
pack = []
model.views['Source'].focus()
model.views['Source'].SetFocus()
model.views['Source'].gotoLine(lineno - 1)
def generatePage(self):
return self.report
def OnSaveReport(self, event):
fn, ok = self.model.editor.saveAsDlg(\
os.path.splitext(self.model.filename)[0]+'.cycles', '*.cycles')
if ok:
from Explorers.Explorer import openEx
transport = openEx(fn)
transport.save(transport.currentFilename(), self.report, 'w')
# XXX Add addReportColumns( list of name, width tuples) !
class ListCtrlView(wx.ListView, EditorView, Utils.ListCtrlSelectionManagerMix):
viewName = 'List (abstract)'
def __init__(self, parent, model, listStyle, actions, dclickActionIdx=-1):
wx.ListView.__init__(self, parent, -1,
style=listStyle | wx.SUNKEN_BORDER | wx.LC_SINGLE_SEL)
EditorView.__init__(self, model, actions, dclickActionIdx,
overrideDClick=True)
Utils.ListCtrlSelectionManagerMix.__init__(self)
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelect)
self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselect)
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivate)
# To catch enter to emulate activated event (bug with notebook and key events on windows)
if wx.Platform == '__WXMSW__':
self.Bind(wx.EVT_KEY_UP, self.OnKeyPressed)
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
self.selected = -1
self.sortOnColumns = []
self.sortCol = -1
self.sortData = {}
self.active = True
self.flipDir = False
self._columnCount = 0
def pastelPicker(self, idx):
return idx % 2
def pastelise(self):
if Preferences.pastels:
for idx in range(self.GetItemCount()):
item = self.GetItem(idx)
if self.pastelPicker(idx):
item.SetBackgroundColour(Preferences.pastelMedium)
else:
item.SetBackgroundColour(Preferences.pastelLight)
self.SetItem(item)
def refreshCtrl(self):
self.DeleteAllItems()
self.sortData = {}
def addReportItems(self, index, list, imgIdx = None):
if list:
if imgIdx is not None:
self.InsertImageStringItem(index, list[0], imgIdx)
else:
self.InsertStringItem(index, list[0])
self.SetItemData(index, index)
self.sortData[index] = list
col = 1
if len(list) > 1:
for text in list[1:]:
self.SetStringItem(index, col, str(text))
col = col + 1
return index + 1
def addReportColumns(self, columns):
self.DeleteAllColumns()
self._columnCount = 0
for name, width in columns:
self.InsertColumn(self._columnCount, name)
self.SetColumnWidth(self._columnCount, width)
self._columnCount = self._columnCount + 1
def getSelectedIndex(self):
if self.selected == -1:
return -1
else:
return self.GetItemData(self.selected)
def sortColumn(self, itemIdx1, itemIdx2):
item1 = self.sortData[itemIdx1][self.sortCol]
item2 = self.sortData[itemIdx2][self.sortCol]
# try to sort integer columns by int value
try:
i1 = int(item1)
i2 = int(item2)
except (TypeError, ValueError):
pass
else:
item1, item2 = i1, i2
if self.flipDir:
item1, item2 = item2, item1
if item1 < item2: return -1
if item1 > item2: return 1
return 0
def OnKeyPressed(self, event):
key = event.KeyCode()
if key == 13:
if self.defaultActionIdx != -1:
self.actions[self.defaultActionIdx][1](event)
return
event.Skip()
def OnItemSelect(self, event):
self.selected = event.m_itemIndex
def OnItemDeselect(self, event):
self.selected = -1
def OnColClick(self, event):
if event.m_col in self.sortOnColumns:
if self.sortCol == event.m_col:
self.flipDir = not self.flipDir
else:
self.sortCol = event.m_col
self.flipDir = False
self.SortItems(self.sortColumn)
self.pastelise()
def OnItemActivate(self, event):
if self.defaultActionIdx < len(self.actions) and self.defaultActionIdx > -1:
self.actions[self.defaultActionIdx][1](event)
# EVT_LEFT_DCLICK(self, self.actions[self.dclickActionIdx][1])
class VirtualListCtrlView(wx.ListCtrl, EditorView):
""" Simple virtual list ctrl
Derived classes must implement
def OnGetItemText(self, item, col):
and call self.SetItemCount(