#----------------------------------------------------------------------------- # Name: ErrorStackFrm.py # Purpose: # # Author: Riaan Booysen # # Created: 2001 # RCS-ID: $Id: ErrorStackFrm.py,v 1.26 2005/05/18 13:19:42 riaan Exp $ # Copyright: (c) 2001 - 2005 Riaan Booysen # Licence: GPL #----------------------------------------------------------------------------- ##Boa:Frame:ErrorStackMF # remove between #-- comments before editing visually import os, pickle import wx import wx.stc import Preferences, Utils [wxID_EO_LOADHIST, wxID_EO_SAVEHIST, wxID_EO_CLRHIST, wxID_EO_CLOSEDIFF, wxID_EO_CLOSEINPT, wxID_EO_KILLPROC, wxID_EO_CHECKPROCS] = Utils.wxNewIds(7) [wxID_ERRORSTACKMFSTATUSBAR, wxID_ERRORSTACKMFERRORSTACKTC, wxID_ERRORSTACKMF, wxID_ERRORSTACKMFNOTEBOOK, wxID_ERRORSTACKMFOUTPUTTC, wxID_ERRORSTACKMFERRORTC] = [wx.NewId() for _init_ctrls in range(6)] class ErrorStackMF(wx.Frame, Utils.FrameRestorerMixin): def _init_coll_notebook_Pages(self, parent): parent.AddPage(select = True, imageId = self.tracebackImgIdx, page = self.errorStackTC, text = self.tracebackText) parent.AddPage(select = False, imageId = self.outputImgIdx, page = self.outputTC, text = self.outputText) parent.AddPage(select = False, imageId = self.errorsImgIdx, page = self.errorTC, text = self.errorsText) def _init_coll_statusBar_Fields(self, parent): parent.SetFieldsCount(1) parent.SetStatusText('', 0) parent.SetStatusWidths([-1]) def _init_utils(self): self.images = wx.ImageList(16, 16) def _init_ctrls(self, prnt): wx.Frame.__init__(self, size = wx.Size(330, 443), id = wxID_ERRORSTACKMF, title = 'Traceback and Output browser', parent = prnt, name = 'ErrorStackMF', style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_TOOL_WINDOW, pos = wx.Point(464, 228)) self._init_utils() self.Bind(wx.EVT_CLOSE, self.OnErrorstackmfClose) self.notebook = wx.Notebook(size = wx.Size(330, 418), id = wxID_ERRORSTACKMFNOTEBOOK, parent = self, name = 'notebook', style = self.notebookStyle, pos = wx.Point(0, 0)) self.statusBar = wx.StatusBar(id = wxID_ERRORSTACKMFSTATUSBAR, parent = self, name = 'statusBar', style = 0) self.SetStatusBar(self.statusBar) self.outputTC = wx.TextCtrl(size = wx.Size(326, 384), value = '', pos = wx.Point(0, 0), parent = self.notebook, name = 'outputTC', style=wx.TE_MULTILINE | wx.TE_RICH, id = wxID_ERRORSTACKMFOUTPUTTC) self.errorTC = wx.TextCtrl(size = wx.Size(326, 384), value = '', pos = wx.Point(0, 0), parent = self.notebook, name = 'errorTC', style=wx.TE_MULTILINE | wx.TE_RICH, id = wxID_ERRORSTACKMFERRORTC) self.errorTC.SetForegroundColour(wx.Colour(64, 0, 0)) #-- # Special case to fix GTK redraw problem if wx.Platform == '__WXGTK__': prxy, errorStackTC = Utils.wxProxyPanel(self.notebook, wx.TreeCtrl, size = wx.Size(312, 390), id = wxID_ERRORSTACKMFERRORSTACKTC, name = 'errorStackTC', style=wx.TR_HAS_BUTTONS | wx.SUNKEN_BORDER, pos = wx.Point(0, 0)) self.errorStackTC = prxy self._init_coll_notebook_Pages(self.notebook) self.errorStackTC = errorStackTC else: self.errorStackTC = wx.TreeCtrl(size = wx.Size(312, 390), id = wxID_ERRORSTACKMFERRORSTACKTC, parent = self.notebook, name = 'errorStackTC', pos = wx.Point(4, 22), style=wx.TR_HAS_BUTTONS | wx.SUNKEN_BORDER) self._init_coll_notebook_Pages(self.notebook) #-- historySize = 50 def __init__(self, parent, editor): self.notebookStyle = 0 self.tracebackImgIdx = 0 self.tracebackText = 'Tracebacks' self.outputImgIdx = 1 self.outputText = 'Output' self.errorsImgIdx = 2 self.errorsText = 'Errors' self.processesPage = None self.processesImgIdx = 3 self.processesText = 'Tasks' self.diffPage = None self.diffImgIdx = 4 self.inputPage = None self.inputImgIdx = 5 self.history = [] self.historyIdx = None if Preferences.eoErrOutNotebookStyle == 'side': self.notebookStyle = wx.NB_LEFT self.tracebackText = ' ' self.outputText = ' ' self.errorsText = ' ' if Preferences.eoErrOutNotebookStyle == 'text': self.tracebackImgIdx = self.outputImgIdx = self.errorsImgIdx = \ self.diffImgIdx = self.inputImgIdx = self.processesImgIdx = -1 self._init_ctrls(parent) self._init_coll_statusBar_Fields(self.statusBar) self.outputTC.SetFont(Preferences.eoErrOutFont) self.errorTC.SetFont(Preferences.eoErrOutFont) self.errorTC.SetForegroundColour(wx.Colour(64, 0, 0)) if Preferences.eoErrOutNotebookStyle == 'side': self.notebook.SetPadding(wx.Size(1, 5)) if Preferences.eoErrOutNotebookStyle != 'text': for img in ('Images/Shared/Traceback.png', 'Images/Shared/Info.png', 'Images/Shared/Error.png', 'Images/Shared/Processes.png', 'Images/CvsPics/Diff.png', 'Images/Shared/Input.png', ): self.images.Add(Preferences.IS.load(img)) self.notebook.AssignImageList(self.images) self.SetIcon(Preferences.IS.load('Images/Icons/OutputError.ico')) self.editor = editor self.vetoEvents = False self.errorStackTC.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnErrorstacktcTreeItemActivated, id=wxID_ERRORSTACKMFERRORSTACKTC) self.errorStackTC.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnErrorstacktcTreeSelChanged, id=wxID_ERRORSTACKMFERRORSTACKTC) #self.errorStackTC.Bind(wx.EVT_LEFT_DOWN, self.OnErrorstacktcLeftDown) #self.lastClick = (0, 0) self.menu = wx.Menu() self.Bind(wx.EVT_MENU, self.OnLoadHistory, id=wxID_EO_LOADHIST) self.menu.Append(wxID_EO_LOADHIST, 'Load history...') self.Bind(wx.EVT_MENU, self.OnSaveHistory, id=wxID_EO_SAVEHIST) self.menu.Append(wxID_EO_SAVEHIST, 'Save history...') self.Bind(wx.EVT_MENU, self.OnClearHistory, id=wxID_EO_CLRHIST) self.menu.Append(wxID_EO_CLRHIST, 'Clear history') self.menu.AppendSeparator() self.Bind(wx.EVT_MENU, self.OnCloseDiff, id=wxID_EO_CLOSEDIFF) self.menu.Append(wxID_EO_CLOSEDIFF, 'Close diff page') self.menu.Enable(wxID_EO_CLOSEDIFF, False) self.Bind(wx.EVT_MENU, self.OnCloseInput, id=wxID_EO_CLOSEINPT) self.menu.Append(wxID_EO_CLOSEINPT, 'Close input page') self.menu.Enable(wxID_EO_CLOSEINPT, False) self.notebook.Bind(wx.EVT_RIGHT_UP, self.OnRightDown) self.processesMenu = wx.Menu() self.Bind(wx.EVT_MENU, self.OnKillProcess, id=wxID_EO_KILLPROC) self.processesMenu.Append(wxID_EO_KILLPROC, 'Kill process') self.processesMenu.AppendSeparator() self.Bind(wx.EVT_MENU, self.OnCheckProcesses, id=wxID_EO_CHECKPROCS) self.processesMenu.Append(wxID_EO_CHECKPROCS, 'Check processes') self.displayProcesses() self.processesPage.Bind(wx.EVT_RIGHT_DOWN, self.OnProcessesRightDown) self.winConfOption = 'errout' if Preferences.eoErrOutDockWindow == 'undocked': self.loadDims() def setDefaultDimensions(self): self.SetDimensions(0, Preferences.paletteHeight + Preferences.windowManagerTop + \ Preferences.windowManagerBottom, Preferences.inspWidth, Preferences.bottomHeight) def addTracebackNode(self, err, parsedTracebacks): tree = self.errorStackTC root = tree.GetRootItem() if err.error and err.stack: errTI = tree.AppendItem(root, ' : '.join(err.error).strip()) for si in err.stack: siTI = tree.AppendItem(errTI, '%d: %s: %s' % (si.lineNo, os.path.basename(si.file), si.line.strip())) tree.SetPyData(siTI, si) if err.stack: tree.SetItemHasChildren(errTI, True) tree.SetPyData(errTI, err.stack[-1]) parsedTracebacks += 1 return parsedTracebacks def updateCtrls(self, errorList, outputList=None, rootName='Error', runningDir='', errRaw=None, addToHistory=True): if addToHistory: if errorList or outputList or errRaw: self.history.append( (errorList, outputList, rootName, runningDir, errRaw) ) while len(self.history) > self.historySize: del self.history[0] self.historyIdx = None self.runningDir = runningDir self.tracebackType = rootName tree = self.errorStackTC tree.DeleteAllItems() rtTI = tree.AddRoot(rootName+'s') parsedTracebacks = 0 for err in errorList: parsedTracebacks += self.addTracebackNode(err, parsedTracebacks) tree.SetItemHasChildren(rtTI, True) tree.Expand(rtTI) firstErr, cookie = tree.GetFirstChild(rtTI) if firstErr.IsOk(): tree.Expand(firstErr) if outputList: self.outputTC.SetValue(''.join(outputList)) else: self.outputTC.SetValue('') if errRaw: self.errorTC.SetValue(''.join(errRaw)) else: self.errorTC.SetValue('') selIdx = -1 if parsedTracebacks: selIdx = 0 elif errorList: selIdx = 2 elif outputList: selIdx = 1 elif errRaw: selIdx = 2 if selIdx >= 0: self.notebook.SetSelection(selIdx) tree.Refresh() return parsedTracebacks def display(self, errs=None): # XXX errs not used ! # docked on this frame if self.notebook.GetParent() == self: self.Show() # docked in inspector elif self.notebook.GetGrandParent() == self.editor.inspector.pages: inspPages = self.notebook.GetGrandParent() inspPages.SetFocus() for idx in range(inspPages.GetPageCount()-1, -1, -1): if inspPages.GetPageText(idx) == 'ErrOut': inspPages.SetSelection(idx) break # docked in editor elif self.notebook.GetGrandParent() == self.editor: splitter = self.editor.tabsSplitter win2 = splitter.GetWindow2() if win2 and win2.GetSize().y == splitter.GetMinimumPaneSize(): splitter.openBottomWindow() def appendToTextCtrl(self, tc, txt, TEXTCTRL_MAXLEN=30000, TEXTCTRL_GOODLEN=20000): # Before appending to the output, remove old data. cursz = tc.GetLastPosition() newsz = cursz + len(txt) if newsz >= TEXTCTRL_MAXLEN: olddata = tc.GetValue()[newsz - TEXTCTRL_GOODLEN:] tc.SetValue(olddata) tc.SetFocus() tc.AppendText(txt) # XXX just make editor err out visible for now, the others would be # XXX too jarring if self.notebook.GetGrandParent() == self.editor: splitter = self.editor.tabsSplitter win2 = splitter.GetWindow2() if win2 and not win2.GetSize().y: for i in range(self.notebook.GetPageCount()): # Try to show the tab where the text was just added. if self.notebook.GetPage(i) == tc: self.notebook.SetSelection(i) tc.Refresh() break splitter.openBottomWindow() def appendToOutput(self, txt): self.appendToTextCtrl(self.outputTC, txt) def appendToErrors(self, txt): self.appendToTextCtrl(self.errorTC, txt) def Destroy(self): self.menu.Destroy() self.vetoEvents = True wx.Frame.Destroy(self) def findPage(self, name): for idx in range(self.notebook.GetPageCount()): if self.notebook.GetPageText(idx) == name: return idx return -1 def displayDiff(self, diffResult): if not self.diffPage: self.diffPage = wx.stc.StyledTextCtrl(self.notebook, -1, style=wx.SUNKEN_BORDER | wx.CLIP_CHILDREN) self.diffPage.SetMarginWidth(1, 0) self.diffPage.SetLexer(wx.stc.STC_LEX_DIFF) self.diffPage.StyleClearAll() # XXX Should be moved to StyleEditor fontPropStr = 'face:%s,size:%d'%( Preferences.eoErrOutFont.GetFaceName(), Preferences.eoErrOutFont.GetPointSize()) for num, style in ( (wx.stc.STC_DIFF_DEFAULT, fontPropStr), (wx.stc.STC_DIFF_COMMENT, fontPropStr+',back:#EEEEFF'), # comment (wx.stc.STC_DIFF_COMMAND, fontPropStr+',fore:#FFFFCC,back:#000000,bold'), #diff (wx.stc.STC_DIFF_HEADER, fontPropStr+',back:#FFFFCC'), #"--- ","+++ ", (wx.stc.STC_DIFF_POSITION, fontPropStr+',back:#CCCCFF,bold'), #'@' (wx.stc.STC_DIFF_DELETED, fontPropStr+',back:#FFCCCC'), #'-' (wx.stc.STC_DIFF_ADDED, fontPropStr+',back:#CCFFCC'), #'+' ): self.diffPage.StyleSetSpec(num, style) self.diffPage.SetText(diffResult) self.notebook.AddPage(text='Diffs', select=not not diffResult, page=self.diffPage, imageId=self.diffImgIdx) self.menu.Enable(wxID_EO_CLOSEDIFF, True) else: self.diffPage.SetText(diffResult) if diffResult: pageIdx = self.findPage('Diffs') if pageIdx != -1: self.notebook.SetSelection(pageIdx) self.display() displayPageIdx = 3 def displayInput(self, display=True): if not self.inputPage: self.inputPage = wx.TextCtrl(self.notebook, -1, value='', style=wx.TE_MULTILINE | wx.TE_RICH | wx.SUNKEN_BORDER | wx.CLIP_CHILDREN) self.inputPage.Bind(wx.EVT_LEFT_DCLICK, self.OnInputDoubleClick) self.notebook.InsertPage(self.displayPageIdx, self.inputPage, 'Input', True, self.inputImgIdx) self.menu.Enable(wxID_EO_CLOSEINPT, True) else: self.notebook.SetSelection(self.displayPageIdx) self.display() def displayProcesses(self): if not self.processesPage: self.processesPage = wx.ListView(self.notebook, -1, style=wx.LC_LIST | wx.LC_ALIGN_TOP) self.notebook.AddPage(self.processesPage, self.processesText, False, self.processesImgIdx) else: pageIdx = self.findPage(self.processesText) if pageIdx != -1: self.notebook.SetSelection(pageIdx) def processStarted(self, name, pid, script='', processType=''): if self.processesPage: idx = self.processesPage.GetItemCount() if script: name = '%s (%s)'%(name, script) self.processesPage.InsertStringItem(idx, '%s : %s'%(name, pid)) self.processesPage.SetItemData(idx, pid) self.checkProcesses() def processFinished(self, pid): if self.processesPage: for idx in range(self.processesPage.GetItemCount()): if self.processesPage.GetItemData(idx) == pid: self.processesPage.DeleteItem(idx) break self.checkProcesses() def checkProcesses(self): if self.processesPage: idxs = range(self.processesPage.GetItemCount()) idxs.reverse() for idx in idxs: pid = self.processesPage.GetItemData(idx) if not wx.Process.Exists(pid): self.processesPage.DeleteItem(idx) def killProcess(self, pid): res = wx.Process.Kill(pid, wx.SIGTERM) if res == wx.KILL_ERROR: res = wx.Process.Kill(pid, wx.SIGKILL) if res == wx.KILL_ERROR: wx.LogError('Cannot kill process %d.'%pid) if res == wx.KILL_ACCESS_DENIED: wx.LogError('Cannot kill process %d, access denied.'%pid) elif res == wx.KILL_OK: self.editor.setStatus('Killed process %d.'%pid) def checkProcessesAtExit(self): if self.processesPage: self.checkProcesses() wx.Yield() cnt = self.processesPage.GetItemCount() if cnt: self.display() pageIdx = self.findPage(self.processesText) self.notebook.SetSelection(pageIdx) if wx.MessageBox('There are still running processes that were ' 'started from Boa, please close or kill them ' 'before quitting.\n\nClick Cancel to quit anyway.', 'Child processes running', wx.ICON_WARNING | wx.OK | wx.CANCEL) == wx.CANCEL: return True return False return True def stepBackInHistory(self): if len(self.history) > 1: if self.historyIdx is None: self.historyIdx = max(len(self.history)-2, 0) else: self.historyIdx = max(self.historyIdx - 1, 0) self.updateCtrls( *(self.history[self.historyIdx]+(False,))) def stepFwdInHistory(self): if len(self.history) > 1 and self.historyIdx is not None: self.historyIdx = min(self.historyIdx + 1, len(self.history)-1) self.updateCtrls( *(self.history[self.historyIdx]+(False,))) def OnErrorstacktcTreeItemActivated(self, event): data = self.errorStackTC.GetPyData(event.GetItem()) if data is None: return if data.file.find('://') != -1 or os.path.isabs(data.file): fn = data.file # elif self.app: # fn = os.path.join(os.path.dirname(self.app.filename), data.file) elif self.runningDir: fn = os.path.join(self.runningDir, data.file) else: print 'no running dir for relative path' fn = os.path.abspath(data.file) model, controller = self.editor.openOrGotoModule(fn)#, self.app) srcView = model.getSourceView() srcView.focus() srcView.gotoLine(data.lineNo - 1) srcView.setLinePtr(data.lineNo - 1) self.editor.setStatus(' : '.join(data.error), self.tracebackType) def OnErrorstackmfClose(self, event): self.Show(True) self.Show(False) def OnErrorstacktcTreeSelChanged(self, event): if self.vetoEvents: return selLine = self.errorStackTC.GetItemText(event.GetItem()) if wx.Platform == '__WXGTK__': self.errorStackTC.SetToolTipString(selLine) self.statusBar.SetStatusText(selLine) ## def OnErrorstacktcLeftDown(self, event): ## self.lastClick = event.GetPosition().asTuple() ## event.Skip() def OnInputDoubleClick(self, event): filename = self.editor.openFileDlg() if filename: from Explorers import Explorer self.inputPage.SetValue(Explorer.openEx(filename).load()) def OnRightDown(self, event): sp = self.notebook.ClientToScreen(event.GetPosition()) mp = self.ScreenToClient(sp) self.PopupMenu(self.menu, mp) def OnLoadHistory(self, event): fn = self.editor.openFileDlg('AllFiles') if fn: from Explorers import Explorer data = Explorer.openEx(fn).load() self.history = pickle.loads(data) def OnSaveHistory(self, event): fn, ok = self.editor.saveAsDlg('history.pcl', 'AllFiles') if ok: data = pickle.dumps(self.history) from Explorers import Explorer n = Explorer.openEx(fn) n.save(n.resourcepath, data) def OnCloseDiff(self, event): if self.diffPage: pageIdx = self.findPage('Diffs') if pageIdx != -1: self.notebook.DeletePage(pageIdx) self.diffPage = None self.menu.Enable(wxID_EO_CLOSEDIFF, False) def OnCloseInput(self, event): if self.inputPage: self.notebook.DeletePage(3) self.inputPage = None self.menu.Enable(wxID_EO_CLOSEINPT, False) def OnClearHistory(self, event): self.updateCtrls([], addToHistory=False) self.history = [] self.historyIdx = None self.editor.setStatus('History cleared.') def OnProcessesRightDown(self, event): ## event.Skip() ## wx.Yield() sp = self.processesPage.ClientToScreen(event.GetPosition()) mp = self.ScreenToClient(sp) self.PopupMenu(self.processesMenu, mp) def OnKillProcess(self, event): if self.processesPage: idx = self.processesPage.GetFirstSelected() while idx != -1: pid = self.processesPage.GetItemData(idx) self.killProcess(pid) idx = self.processesPage.GetNextSelected(idx) self.checkProcesses() def OnCheckProcesses(self, event): if self.processesPage: self.checkProcesses() if __name__ == '__main__': app = wx.PySimpleApp() wx.InitAllImageHandlers() frame = ErrorStackMF(None, None) frame.Show(True) app.MainLoop()