#----------------------------------------------------------------------------- # Name: HelpBook.py # Purpose: # # Author: Riaan Booysen # # Created: 2003 # RCS-ID: $Id: HelpBook.plug-in.py,v 1.10 2005/05/18 12:11:50 riaan Exp $ # Copyright: (c) 2003 - 2005 # Licence: GPL #----------------------------------------------------------------------------- #Boa:Dialog:HelpBookItemDlg import wx def createIndexDlg(parent, keyword, location, anchors): return HelpBookItemDlg(parent, 'Help Book - Index', keyword, 'Keyword(s)', '', location, 'Location', anchors) def createContentsDlg(parent, title, htmlTitle, location, anchors): return HelpBookItemDlg(parent, 'Help Book - Contents', title, 'Title', htmlTitle, location, 'Location', anchors) [wxID_HELPBOOKITEMDLG, wxID_HELPBOOKITEMDLGBTNREADTITLE, wxID_HELPBOOKITEMDLGBUTTON2, wxID_HELPBOOKITEMDLGBUTTON3, wxID_HELPBOOKITEMDLGCBBANCHORS, wxID_HELPBOOKITEMDLGSTATICTEXT1, wxID_HELPBOOKITEMDLGSTATICTEXT2, wxID_HELPBOOKITEMDLGTXTPAGE, wxID_HELPBOOKITEMDLGTXTTITLE, ] = [wx.NewId() for _init_ctrls in range(9)] class HelpBookItemDlg(wx.Dialog): def _init_ctrls(self, prnt): # generated method, don't edit wx.Dialog.__init__(self, id=wxID_HELPBOOKITEMDLG, name='HelpBookItemDlg', parent=prnt, pos=wx.Point(452, 359), size=wx.Size(446, 136), style=wx.DEFAULT_DIALOG_STYLE, title=self.caption) self.SetClientSize(wx.Size(438, 109)) self.staticText1 = wx.StaticText(id=wxID_HELPBOOKITEMDLGSTATICTEXT1, label=self.nameLabel, name='staticText1', parent=self, pos=wx.Point(8, 16), size=wx.Size(56, 13), style=0) self.txtTitle = wx.TextCtrl(id=wxID_HELPBOOKITEMDLGTXTTITLE, name='txtTitle', parent=self, pos=wx.Point(72, 8), size=wx.Size(232, 21), style=0, value=self.name) self.staticText2 = wx.StaticText(id=wxID_HELPBOOKITEMDLGSTATICTEXT2, label=self.valueLabel, name='staticText2', parent=self, pos=wx.Point(8, 48), size=wx.Size(64, 13), style=0) self.txtPage = wx.TextCtrl(id=wxID_HELPBOOKITEMDLGTXTPAGE, name='txtPage', parent=self, pos=wx.Point(72, 40), size=wx.Size(232, 21), style=0, value=self.value) self.cbbAnchors = wx.ComboBox(choices=self.valueAnchors, id=wxID_HELPBOOKITEMDLGCBBANCHORS, name='cbbAnchors', parent=self, pos=wx.Point(304, 40), size=wx.Size(125, 21), style=0, value=self.valueAnchorsValue) self.btnReadTitle = wx.Button(id=wxID_HELPBOOKITEMDLGBTNREADTITLE, label='Title from HTML', name='btnReadTitle', parent=self, pos=wx.Point(304, 8), size=wx.Size(123, 23), style=0) self.btnReadTitle.Bind(wx.EVT_BUTTON, self.OnBtnreadtitleButton, id=wxID_HELPBOOKITEMDLGBTNREADTITLE) self.button2 = wx.Button(id=wx.ID_OK, label='OK', name='button2', parent=self, pos=wx.Point(272, 72), size=wx.Size(75, 23), style=0) self.button3 = wx.Button(id=wx.ID_CANCEL, label='Cancel', name='button3', parent=self, pos=wx.Point(353, 72), size=wx.Size(75, 23), style=0) def __init__(self, parent, caption, name, nameLabel, nameDefault, value, valueLabel, valueAnchors): self.caption = 'Help Book' self.caption = caption self.name = '' self.name = name self.nameLabel = 'Name' self.nameLabel = nameLabel self.nameDefault = '' self.nameDefault = nameDefault self.value = '' self.value = self.getFilenameValue(value) self.valueLabel = 'Value' self.valueLabel = valueLabel self.valueAnchors = [] self.valueAnchors = valueAnchors self.valueAnchorsValue = '' self.valueAnchorsValue = self.getAnchorValue(value) self._init_ctrls(parent) self.titleFromHtml = '' if not self.nameDefault: self.btnReadTitle.Disable() self.button2.SetDefault() def GetResult(self): name = self.txtTitle.GetValue() anch = self.cbbAnchors.GetValue().strip() value = self.txtPage.GetValue() if anch: value = value + '#' + anch return name, value def OnBtnreadtitleButton(self, event): self.txtTitle.SetValue(self.titleFromHtml) #------------------------------------------------------------------------------- def getAnchorValue(self, value): if value.find('#') != -1: return value.split('#', 1)[1] else: return '' def getFilenameValue(self, value): if value.find('#') != -1: return value.split('#')[0] else: return value import os, sys, htmllib, formatter if __name__ == '__main__': sys.path.append('..') class HelpConfigParser: def __init__(self, lines, defaultName='helpbook'): self.options = {'Compatibility': '1.1', 'Full-text search': 'Yes', 'Default Window': 'wxHelp', 'Default topic': 'index.html', 'Title': defaultName, } self.updateFilenameOptions(defaultName) self.windows = [] self.files = [] self._section = '' for line in [l.strip() for l in lines if l.strip()]: if line in ('[OPTIONS]', '[WINDOWS]', '[FILES]'): self._section = line else: if self._section == '[OPTIONS]': k, v = line.split('=') self.options[k] = v elif self._section == '[WINDOWS]': k, v = line.split('=') self.windows = v.split(',') elif self._section == '[FILES]': self.files.append(line) def generateConfigData(self): res = ['[OPTIONS]'] res.extend(['%s=%s'%(key, val) for key, val in self.options.items()]) res.append('') res.append('[WINDOWS]') # I have no idea what the magic numbers mean res.append('wxHelp=,"%(Contents file)s","%(Index file)s",' '"%(Default topic)s",,,,,,0x2420,,0x380e,,,,,0,,,'%self.options) res.append('') res.append('[FILES]') res.extend(self.files) return '\n'.join(res) def updateFilenameOptions(self, name): self.options.update({'Contents file': name+'.hhc', 'Compiled file': name+'.chm', 'Index file': name+'.hhk'}) class HelpBookParser(htmllib.HTMLParser): def __init__(self, formatter, verbose=0): htmllib.HTMLParser.__init__(self, formatter, verbose) self.indexes = None self.index = None self.workstack = [] self.result = [] def start_ul(self, attrs): indexes = [] if self.indexes is not None: self.workstack.append(self.indexes) if self.indexes: self.indexes[-1] = self.indexes[-1][:2]+(indexes,) self.indexes = indexes self.index = None def end_ul(self): if not self.workstack: self.result = self.indexes else: self.indexes = self.workstack.pop() def do_param(self, attrs): if self.indexes is not None and self.index: name, value = attrs[0][1], attrs[1][1] self.index[name == 'Local'] = value def start_object(self, attrs): if self.indexes is not None and self.index is None: self.index = [None, None, None] def end_object(self): if self.indexes is not None: self.indexes.append(tuple(self.index)) self.index = None def writeHelpBook(items): res = ['') return '\n'.join(res) def parseHelpFile(data, Parser=HelpBookParser): w = formatter.NullWriter() f = formatter.NullFormatter(w) p = Parser(f) p.feed(data) return p class BreakOnTitle(Exception): pass class HtmlDocDetailParser(htmllib.HTMLParser): def __init__(self, formatter, verbose=0, breakOnTitle=False): htmllib.HTMLParser.__init__(self, formatter, verbose) self.anchors = [] self.title = '' self.breakOnTitle = breakOnTitle self.result = [self.title, self.anchors] _in_title = False def start_title(self, attrs): self._in_title = True def end_title(self): self._in_title = False def start_a(self, attrs): if attrs and attrs[0] and attrs[0][0].lower() == 'name': self.anchors.append(attrs[0][1]) def end_a(self): pass def handle_data(self, data): if self._in_title: self.title = self.result[0] = data.strip() if self.breakOnTitle: raise BreakOnTitle, self.title def do_param(self, attrs): pass#print 'do_param', attrs def _testHPP(): from pprint import pprint #pprint(parseHelpFile(open('../Docs/python/python.hhc').read())) #i = parseHelpFile(open('../Docs/boa/apphelp/apphelp.hhc').read()) #print i ##writeHelpBook(i) print parseHelpFile(open('../Docs/boa/apphelp/debugger.html').read(), Parser=HtmlDocDetailParser).result def _testHCP(): h = HelpConfigParser(open('../Docs/wxpython/wx/wx.hhp').readlines()) print h.options, h.windows, h.files if __name__ == '__main__': _testHPP() #_testHCP() #------------------------------------------------------------------------------- import Preferences, Utils, Plugins import PaletteStore from Models import Controllers, EditorHelper, EditorModels from Views import EditorViews, SourceViews, StyledTextCtrls from Explorers import Explorer, ExplorerNodes import ProcessProgressDlg import glob, zipfile from cStringIO import StringIO class HelpBookModel(EditorModels.SourceModel): modelIdentifier = 'HelpBook' defaultName = 'helpbook' bitmap = 'HelpBook.png' ext = '.hhp' imgIdx = EditorHelper.imgHelpBook def __init__(self, data, name, editor, saved): EditorModels.SourceModel.__init__(self, data, name, editor, saved) contents = os.path.splitext(name)[0]+'.hhc' try: transport = Explorer.openEx(contents) data = transport.load() except ExplorerNodes.TransportError: transport = None data = '' self.contentsModel = EditorModels.SourceModel(data, contents, editor, saved) self.contentsModel.transport = transport indexes = os.path.splitext(name)[0]+'.hhk' try: transport = Explorer.openEx(indexes) data = transport.load() except ExplorerNodes.TransportError: transport = None data = '' self.indexesModel = EditorModels.SourceModel(data, indexes, editor, saved) self.indexesModel.transport = transport self.update() def update(self): self.config = HelpConfigParser(StringIO(self.data).readlines()) self.contentsModel.update() self.contents = parseHelpFile(self.contentsModel.data).result self.indexesModel.update() self.indexes = parseHelpFile(self.indexesModel.data).result def load(self, notify=True): try: self.contentsModel.load(False) except ExplorerNodes.TransportError: pass try: self.indexesModel.load(False) except ExplorerNodes.TransportError: pass EditorModels.SourceModel.load(self, notify) def save(self, overwriteNewer=False): if self.modified: gen = '\n' if self.contentsModel.modified: self.contentsModel.data = gen + writeHelpBook(self.contents) self.contentsModel.save(overwriteNewer) if self.indexesModel.modified: self.indexesModel.data = gen + writeHelpBook(self.indexes) self.indexesModel.save(overwriteNewer) self.data = self.config.generateConfigData() EditorModels.SourceModel.save(self, overwriteNewer) def saveAs(self, filename): newDir, newName = os.path.split(filename) oldDir, oldName = os.path.split(self.filename) if self.savedAs and newDir != oldDir: raise ExplorerNodes.TransportSaveError, \ 'Once saved, help books files cannot be moved, only renamed.\n'\ 'Please move the entrire directory.' if newName != oldName: self.config.updateFilenameOptions(os.path.splitext(newName)[0]) self.modified = True noExtFilename = os.path.splitext(filename)[0] self.contentsModel.saveAs(noExtFilename+'.hhc') self.indexesModel.saveAs(noExtFilename+'.hhk') EditorModels.SourceModel.saveAs(self, filename) def setModified(self, section='config'): if section == 'contents': self.contentsModel.modified = True elif section == 'indexes': self.indexesModel.modified = True self.modified = True def makeHTB(self): pass # XXX Experimental, should just load and save from zip class HTBHelpBookModel(HelpBookModel): modelIdentifier = 'BTBHelpBook' defaultName = 'htbhelpbook' ext = '.htb' def __init__(self, data, name, editor, saved): HelpBookModel.__init__(self, data, name, editor, saved) def load(self, notify=True): pass def save(self, overwriteNewer=False): pass ## if self.GetItemCount(): ## itemFrom = self.GetTopItem() ## itemTo = self.GetTopItem()+1 + self.GetCountPerPage() ## itemTo = min(itemTo, self.GetItemCount()-1) ## self.RefreshItems(itemFrom, itemTo) class HelpBookFilesView(EditorViews.VirtualListCtrlView): viewName = 'Files' addBmp = 'Images/Shared/NewItem.png' delBmp = 'Images/Shared/DeleteItem.png' def __init__(self, parent, model, provideActions=True): if provideActions: actions = (('Add file', self.OnAddFile, self.addBmp, ''), ('Add files', self.OnAddFiles, '-', ''), ('Remove file', self.OnRemoveFile, self.delBmp, ''), ('Open file', self.OnOpenFile, '-', ''), ('-', None, '-', ''), ('Normalise paths', self.OnNormalisePaths, '-', ''),) else: actions = () EditorViews.VirtualListCtrlView.__init__(self, parent, model, wx.LC_REPORT, actions, -1) self.Bind(wx.EVT_LEFT_DOWN, self.OnFilesLeftDown) #self.sortOnColumns = [0, 1, 2] self.InsertColumn(0, 'No') self.InsertColumn(1, 'Name') self.InsertColumn(2, 'Path') self.SetColumnWidth(0, 30) self.SetColumnWidth(1, 150) self.SetColumnWidth(2, 300) if Preferences.hbShowDocumentTitles: self.InsertColumn(3, 'Title') self.SetColumnWidth(3, 300) self.active = True self.resetCache() #self.Bind(wx.EVT_LIST_CACHE_HINT, self.OnCacheHint, id=self.GetId()) def refreshCtrl(self): self.resetCache() self.SetItemCount(len(self.model.config.files)) def OnGetItemText(self, item, col): f = self.model.config.files[item] if col == 0: return `item` elif col == 1: return os.path.basename(f) elif col == 2: return f elif Preferences.hbShowDocumentTitles and col == 3: if not self.cached[item]: title = '' try: if os.path.splitext(f)[1].lower() not in ('.htm', '.html'): return '' docsDir = os.path.dirname(self.model.filename) try: data = Explorer.openEx(os.path.join(docsDir, f)).load() except ExplorerNodes.TransportError: return '' fmtr = formatter.NullFormatter(formatter.NullWriter()) try: HtmlDocDetailParser(fmtr, breakOnTitle=True).feed(data) except BreakOnTitle, title: return str(title) except: return '' else: return '' finally: self.cached[item] = title else: return self.cached[item] def doStartDrag(self): sel = self.GetSelections() if sel: filenames = [self.model.config.files[idx] for idx in sel] filelist = wx.CustomDataObject(wx.CustomDataFormat('FileList')) filelist.SetData(`filenames`) #tdo =wx.TextDataObject(filename) ds = wx.DropSource(self) ds.SetData(filelist) ds.DoDragDrop(wx.Drag_DefaultMove) def OnFilesLeftDown(self, event): event.Skip() wx.CallAfter(self.doStartDrag) helpBookDirMsg = 'Help documents must be in the same directory as the help '\ 'book, or a sub-directory. Not allowed outside the tree.' def OnAddFile(self, event): if not self.model.savedAs: wx.LogError('Please save the help book before adding files\n'+self.helpBookDirMsg) return filename = self.model.editor.openFileDlg() if filename: helpProjDir = os.path.dirname(self.model.filename) if not filename.startswith(helpProjDir): wx.LogError(self.helpBookDirMsg) return self.model.config.files.append(filename[len(helpProjDir)+1:]) self.refreshCtrl() self.updateOtherViews() self.model.setModified() self.updateEditor() def OnRemoveFile(self, event): sel = self.GetSelections() if sel: sel.sort() sel.reverse() for idx in sel: del self.model.config.files[idx] self.refreshCtrl() self.updateOtherViews() self.model.setModified() self.updateEditor() def updateOtherViews(self): views = self.model.views if views.has_key('Contents'): views['Contents'].files.refreshCtrl() if views.has_key('Index'): views['Index'].files.refreshCtrl() def OnOpenFile(self, event): idx = self.selected if idx != -1: helpBookDir = os.path.dirname(self.model.filename) self.model.editor.openOrGotoModule( helpBookDir+'/'+self.model.config.files[idx]) #def OnCacheHint(self, event): # print 'From', event.GetCacheFrom() # print 'To', event.GetCacheTo() def resetCache(self, ): self.cached = [False]*len(self.model.config.files) def OnNormalisePaths(self, event): helpBookDir = os.path.dirname(self.model.localFilename()) delidxs = [] for idx, filename in zip(range(len(self.model.config.files)), self.model.config.files): absfn = os.path.normpath(os.path.join(helpBookDir, filename)) if absfn[:len(helpBookDir)] != helpBookDir: if wx.MessageBox('Filename "%s" not inside help book directory.' '\n\nRemove?'%absfn, 'Invalid file', wx.ICON_QUESTION | wx.YES_NO) == wx.YES: delidxs.append(idx) else: self.model.config.files[idx] = absfn[len(helpBookDir)+1:] if delidxs: delidxs.reverse() for idx in delidxs: del self.model.config.files[idx] self.refreshCtrl() self.updateOtherViews() self.model.setModified() self.updateEditor() def OnAddFiles(self, event): helpBookDir = os.path.dirname(self.model.localFilename()) dlg = wx.DirDialog(self) try: dlg.SetPath(helpBookDir) if dlg.ShowModal() != wx.ID_OK: return dir = dlg.GetPath() finally: dlg.Destroy() if not dir.startswith(helpBookDir): wx.LogError(self.helpBookDirMsg) return dlg = wx.TextEntryDialog(self, 'Enter wildcard', 'Add Files', '*.*') try: if dlg.ShowModal() != wx.ID_OK: return globStr = dlg.GetValue() finally: dlg.Destroy() files = glob.glob(os.path.join(dir, globStr)) for filename in files: self.model.config.files.append(filename[len(helpBookDir)+1:]) self.refreshCtrl() self.updateOtherViews() self.model.setModified() self.updateEditor() class FileListDropTarget(wx.PyDropTarget): def __init__(self): wx.PyDropTarget.__init__(self) self.fmt = wx.CustomDataFormat('FileList') self.data = wx.CustomDataObject(self.fmt) self.SetDataObject(self.data) def OnDrop(self, x, y): return True def OnData(self, x, y, d): if self.GetData(): filelist = eval(self.data.GetData()) self.OnDropFiles(x, y, filelist) return d class HelpBookIndexDropTarget(FileListDropTarget): def __init__(self, indexList): FileListDropTarget.__init__(self) self.list = indexList def OnDropFiles(self, x, y, files): docsDir = os.path.dirname(self.list.model.filename) for filename in files: data = Explorer.openEx(os.path.join(docsDir, filename)).load() prs = parseHelpFile(data, HtmlDocDetailParser) dlg = createIndexDlg(None, '', filename, prs.anchors) try: if dlg.ShowModal() != wx.ID_OK: return keyword, location = dlg.GetResult() finally: dlg.Destroy() #item, flags = self.list.HitTest( (x, y) ) self.list.model.indexes.append((keyword, location, None)) self.list.model.setModified('indexes') self.list.updateEditor() self.list.refreshCtrl() class HelpBookIndexView(wx.SplitterWindow, EditorViews.EditorView): viewName = 'Index' addBmp = 'Images/Shared/NewItem.png' delBmp = 'Images/Shared/DeleteItem.png' def __init__(self, parent, model): wx.SplitterWindow.__init__(self, parent, -1, style=wx.CLIP_CHILDREN | wx.NO_3D | wx.SP_3DSASH) self.indexes = HelpBookIndexListView(self, model, self) self.files = HelpBookFilesView(self, model, False) self.SplitVertically(self.indexes, self.files, 350) EditorViews.EditorView.__init__(self, model, ( ('Add index', self.indexes.OnAddIndex, self.addBmp, ''), ('Edit index', self.indexes.OnEditIndex, '-', ''), ('Delete index', self.indexes.OnDeleteIndex, self.delBmp, ''), ), 0) self.active = True def refreshCtrl(self): self.indexes.refreshCtrl() self.files.refreshCtrl() class HelpBookIndexListView(EditorViews.VirtualListCtrlView): def __init__(self, parent, model, parentView=None): EditorViews.VirtualListCtrlView.__init__(self, parent, model, wx.LC_REPORT, (), -1) self.parentView = parentView self.SetDropTarget(HelpBookIndexDropTarget(self)) #self.sortOnColumns = [0, 1] self.InsertColumn(0, 'Keyword') self.InsertColumn(1, 'Location') self.SetColumnWidth(0, 100) self.SetColumnWidth(1, 400) self.active = True def refreshCtrl(self): self.SetItemCount(len(self.model.indexes)) def OnGetItemText(self, item, col): return self.model.indexes[item][col] def OnRightClick(self, event): if self.parentView: menu = self.parentView.generateMenu() event.GetEventObject().PopupMenuXY(menu, event.GetX(), event.GetY()) menu.Destroy() def OnAddIndex(self, event): dlg = createIndexDlg(None, '', '', []) try: if dlg.ShowModal() != wx.ID_OK: return keyword, location = dlg.GetResult() finally: dlg.Destroy() self.model.indexes.append((keyword, location, None)) self.model.setModified('indexes') self.updateEditor() self.refreshCtrl() def OnEditIndex(self, event): if self.selected != -1: keyword, location, c = self.model.indexes[self.selected] docsDir = os.path.dirname(self.model.filename) try: data = Explorer.openEx( os.path.join(docsDir, location.split('#')[0])).load() except ExplorerNodes.TransportLoadError: data = '' prs = parseHelpFile(data, HtmlDocDetailParser) dlg = createIndexDlg(None, keyword, location, prs.anchors) try: if dlg.ShowModal() != wx.ID_OK: return res = dlg.GetResult() finally: dlg.Destroy() if res != (keyword, location): keyword, location = res self.model.indexes[self.selected] = (keyword, location, None) self.model.setModified('indexes') self.updateEditor() self.refreshCtrl() def OnDeleteIndex(self, event): if self.selected != -1: del self.model.indexes[self.selected] self.model.setModified('indexes') self.updateEditor() self.refreshCtrl() class HelpBookContentsDropTarget(FileListDropTarget): def __init__(self, contentsTree): FileListDropTarget.__init__(self) self.tree = contentsTree def OnDropFiles(self, x, y, files): item, flags = self.tree.HitTest( (x, y) ) if not item.Ok(): return name = self.tree.GetItemText(item) value, items, children = self.tree.GetPyData(item) docsDir = os.path.dirname(self.tree.model.filename) for filename in files: title, page = doContentsDlg(None, filename, docsDir) if (title, page) == (None, None): continue entry = (title, page, None) if children is None: idx = items.index( (name, value, children) ) items[idx] = (name, value, [entry]) children = [entry] else: children.append(entry) self.tree.model.setModified('contents') self.tree.updateEditor() self.tree.SetItemHasChildren(item, True) self.tree.SetItemData(item, wx.TreeItemData((value, items, children))) #if self.tree.GetItemImage(item) != 0: # self.tree.SetItemImage(item, 1, 1) # XXX Doesn't work self.tree.SelectItem(item) self.tree.Collapse(item) self.tree.Expand(item) #self.tree.refreshCtrl() def doContentsDlg(title, text, docsDir): #text, anchor = (text.split('#')+[''])[:2] data = Explorer.openEx(os.path.join(docsDir, text.split('#')[0])).load() prs = parseHelpFile(data, HtmlDocDetailParser) if title is None: title = prs.title dlg = createContentsDlg(None, title, prs.title, text, prs.anchors) try: if dlg.ShowModal() != wx.ID_OK: return None, None title, location = dlg.GetResult() finally: dlg.Destroy() return title, location class HelpBookContentsView(wx.SplitterWindow, EditorViews.EditorView): viewName = 'Contents' addBmp = 'Images/Shared/NewItem.png' delBmp = 'Images/Shared/DeleteItem.png' def __init__(self, parent, model): wx.SplitterWindow.__init__(self, parent, -1, style=wx.CLIP_CHILDREN | wx.NO_3D | wx.SP_3DSASH) self.contents = HelpBookContentsTreeView(self, model, self) self.files = HelpBookFilesView(self, model, False) self.SplitVertically(self.contents, self.files, 350) actions = (('Edit entry', self.contents.OnEditEntry, '-', ''), ('Delete entry', self.contents.OnDeleteEntry, self.delBmp, ''), ('Import...', self.OnImportContents, '-', ''), ) EditorViews.EditorView.__init__(self, model, actions, -1) self.active = True def refreshCtrl(self): self.contents.refreshCtrl() self.files.refreshCtrl() def OnImportContents(self, event): pass class HelpBookContentsTreeView(wx.TreeCtrl, EditorViews.EditorView): viewName = 'Contents' def __init__(self, parent, model, parentView=None): wx.TreeCtrl.__init__(self, parent, -1, style=wx.TR_HAS_BUTTONS | wx.SUNKEN_BORDER | wx.TR_DEFAULT_STYLE) EditorViews.EditorView.__init__(self, model, (), -1) self.parentView = parentView #(('Edit entry', self.OnEditEntry, '-', ''),) self.SetDropTarget(HelpBookContentsDropTarget(self)) self.helpImgLst = wx.ImageList(16, 16) for artId in (wx.ART_HELP_BOOK, wx.ART_HELP_FOLDER, wx.ART_HELP_PAGE): bmp = wx.ArtProvider_GetBitmap(artId, wx.ART_TOOLBAR, (16, 16)) self.helpImgLst.Add(bmp) self.AssignImageList(self.helpImgLst) self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnTreeExpand, id=self.GetId()) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeSelChanged, id=self.GetId()) self.active = True def refreshCtrl(self): self.DeleteAllItems() title = self.model.config.options['Title'] rootItem = self.AddRoot(title, 0, data=wx.TreeItemData((title, None, self.model.contents))) self.SetItemHasChildren(rootItem, True) self.Expand(rootItem) def recurseAddItems(self, items, treeItem): # XXX no longer used, build entire tree recursively for name, value, children in items: item = self.AppendItem(treeItem, name, children and 1 or 2, -1, wx.TreeItemData((value, items, children))) if children: self.recurseAddItems(children, item) self.SetItemHasChildren(item, True) def addTreeItems(self, items, treeItem): self.DeleteChildren(treeItem) for name, value, children in items: item = self.AppendItem(treeItem, name, children and 1 or 2, -1, wx.TreeItemData((value, items, children))) if children: self.SetItemHasChildren(item, True) def OnRightClick(self, event): if self.parentView: menu = self.parentView.generateMenu() event.GetEventObject().PopupMenuXY(menu, event.GetX(), event.GetY()) menu.Destroy() def OnTreeExpand(self, event): item = event.GetItem() self.addTreeItems(self.GetPyData(item)[-1], item) def OnTreeSelChanged(self, event): item = event.GetItem() if item.Ok(): location = self.GetPyData(item)[0] self.model.editor.setStatus(location) def OnEditEntry(self, event): tree = self item = tree.GetSelection() name = tree.GetItemText(item) value, items, children = tree.GetPyData(item) if item == tree.GetRootItem(): options = self.model.config.options dlg = HelpBookItemDlg(self.model.editor, 'Help Book - Contents properties', options['Title'], 'Title', '', options['Default topic'], 'Default topic', []) try: if dlg.ShowModal() != wx.ID_OK: return title, defTopic = dlg.GetResult() finally: dlg.Destroy() if options['Title'] != title or options['Default topic'] != defTopic: self.model.setModified('config') self.updateEditor() options['Title'] = title options['Default topic'] = defTopic tree.SetItemData(item, wx.TreeItemData((title, defTopic, children))) tree.SetItemText(item, title) # XXX mark hhp modified return docsDir = os.path.dirname(self.model.filename) title, page = doContentsDlg(name, value, docsDir) if (title, page) == (None, None): return if (name, value, children) != (title, page, children): idx = items.index( (name, value, children) ) items[idx] = (title, page, children) self.model.setModified('contents') self.updateEditor() tree.SetItemData(item, wx.TreeItemData((page, items, children))) tree.SetItemText(item, title) def OnDeleteEntry(self, event): item = self.GetSelection() if item == self.GetRootItem(): wx.LogError('Cannot delete root node.') return name = self.GetItemText(item) value, items, children = self.GetPyData(item) idx = items.index( (name, value, children) ) if wx.MessageBox('Delete %s'%name, 'Delete node and children', wx.ICON_WARNING | wx.YES_NO) == wx.YES: del items[idx] self.model.modified = True self.updateEditor() self.Collapse(item) self.Delete(item) def visitDir((files, excludes), dirname, names): for name in names: if name not in excludes: filename = os.path.join(dirname, name) if os.path.isfile(filename): files.append(filename) wxID_HP_X = wx.NewId() class HelpBookController(Controllers.SourceController): Model = HelpBookModel DefaultViews = [HelpBookFilesView, HelpBookContentsView, HelpBookIndexView] def actions(self, model): actions = [ ('-', None, '', ''), ('Make HTB', self.OnMakeHTB, '', ''), ] if wx.Platform == '__WXMSW__': actions.append( ('Make CHM', self.OnMakeCHM, '', '') ) return Controllers.SourceController.actions(self, model) + actions def OnMakeHTB(self, event): model = self.getModel() dlg = wx.SingleChoiceDialog(model.editor, 'Choose source files', 'Make HTB', ['Files list', 'Entire help book directory']) try: if dlg.ShowModal() != wx.ID_OK: return selected = dlg.GetSelection() finally: dlg.Destroy() modelFile = model.localFilename() zipfilename = os.path.splitext(modelFile)[0]+'.htb' docsDir = os.path.dirname(modelFile) if selected == 0: files = [os.path.join(docsDir, f) for f in model.config.files] elif selected == 1: files = [] os.path.walk(docsDir, visitDir, (files, [os.path.basename(zipfilename)])) wx.BeginBusyCursor() zf = zipfile.ZipFile(zipfilename, 'w', zipfile.ZIP_DEFLATED) try: for filename in files: zf.write(filename, filename[len(docsDir)+1:]) finally: wx.EndBusyCursor() zf.close() wx.LogMessage('Written %s.'%zipfilename) def OnMakeCHM(self, event): modelFile = model.localFilename() dir, name = os.path.split(modelFile) cmd = 'hhc %s'%name cwd = os.getcwd() try: os.chdir(runDir) dlg = ProcessProgressDlg.ProcessProgressDlg(self.editor, cmd, 'Make CHM') try: if dlg.ShowModal() == wx.OK: outls = dlg.output errls = dlg.errors else: return finally: dlg.Destroy() finally: os.chdir(cwd) #err = ''.join(errls).strip() #------------------------------------------------------------------------------- Plugins.registerPreference('HelpBook', 'hbShowDocumentTitles', 'True', ['Should the document title be parsed from HTML and ' 'displayed under "Files".']) Plugins.registerFileType(HelpBookController)