def exportHtml(self, fileName=False): #self.makeError1() if fileName is False: self.fileDialog = FileDialog(self, "Save HTML as...", self.manager.getCurrentDir().name()) #self.fileDialog.setFileMode(Qt.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(Qt.QFileDialog.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.exportHtml) return if fileName[-5:] != '.html': fileName += '.html' #doc = self.ui.output.document().toHtml('utf-8') #for e in self.displayedEntries: #if e.has_key('tracebackHtml'): #doc = re.sub(r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>'%(str(e['id']), str(e['id'])), e['tracebackHtml'], doc) global pageTemplate doc = pageTemplate for e in self.displayedEntries: doc += self.cache[id(e)] for e in self.displayedEntries: if 'tracebackHtml' in e: doc = re.sub( r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>' % (str(e['id']), str(e['id'])), e['tracebackHtml'], doc) #doc = self.ui.logView.page().currentFrame().toHtml() f = open(fileName, 'w') f.write(doc.encode('utf-8')) f.close()
def showFileDialog(self): bd = self.manager.getBaseDir() if self.dialog is None: self.dialog = FileDialog() self.dialog.setFileMode(Qt.QFileDialog.DirectoryOnly) self.dialog.filesSelected.connect(self.setBaseDir) if bd is not None: self.dialog.setDirectory(bd.name()) self.dialog.show()
def openDbClicked(self): bd = self.man.getBaseDir() if bd is None: bd = "" else: bd = bd.name() self.fileDialog = FileDialog(self, "Select Database File", bd, "SQLite Database (*.sqlite *.sql);;All Files (*.*)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.openDb)
def createDbClicked(self): bd = self.man.getBaseDir() if bd is None: raise Exception("Must select a base directory before creating database.") self.fileDialog = FileDialog(self, "Create Database File", bd.name(), "SQLite Database (*.sqlite *.sql);;All Files (*.*)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.setOption(QtGui.QFileDialog.DontConfirmOverwrite) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.createDb)
def __init__(self, manager, name, config): Module.__init__(self, manager, name, config) #self.dm = self.manager.dataManager self.dm = getDataManager() self.win = Window() mp = os.path.dirname(__file__) self.win.setWindowIcon(QtGui.QIcon(os.path.join(mp, 'icon.png'))) self.win.dm = self ## so embedded widgets can find the module easily self.ui = Ui_MainWindow() self.ui.setupUi(self.win) self.ui.analysisWidget = FileAnalysisView.FileAnalysisView( self.ui.analysisTab, self) self.ui.analysisTab.layout().addWidget(self.ui.analysisWidget) self.ui.logWidget = FileLogView.FileLogView(self.ui.logTab, self) self.ui.logTab.layout().addWidget(self.ui.logWidget) self.win.show() w = self.ui.splitter.width() self.ui.splitter.setSizes([int(w * 0.4), int(w * 0.6)]) self.ui.logDock.hide() self.dialog = FileDialog() self.dialog.setFileMode(QtGui.QFileDialog.DirectoryOnly) self.ui.fileTreeWidget.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) ## Load values into GUI #self.model = DMModel(self.manager.getBaseDir()) #self.ui.fileTreeView.setModel(self.model) self.baseDirChanged() self.currentDirChanged() self.selFile = None self.updateNewFolderList() ## Make all connections needed self.ui.selectDirBtn.clicked.connect(self.showFileDialog) self.ui.setCurrentDirBtn.clicked.connect(self.setCurrentClicked) self.dialog.filesSelected.connect(self.setBaseDir) self.manager.sigBaseDirChanged.connect(self.baseDirChanged) self.manager.sigCurrentDirChanged.connect(self.currentDirChanged) self.manager.sigConfigChanged.connect(self.updateNewFolderList) self.manager.sigLogDirChanged.connect(self.updateLogDir) self.ui.setLogDirBtn.clicked.connect(self.setLogDir) self.ui.newFolderList.currentIndexChanged.connect(self.newFolder) self.ui.fileTreeWidget.itemSelectionChanged.connect( self.fileSelectionChanged) #self.ui.logEntryText.returnPressed.connect(self.logEntry) self.ui.fileDisplayTabs.currentChanged.connect(self.tabChanged) self.win.sigClosed.connect(self.quit) self.ui.analysisWidget.sigDbChanged.connect(self.analysisDbChanged) #self.logBtn = LogButton('Log') self.win.setStatusBar(StatusBar())
def exportHtml(self, fileName=False): # self.makeError1() if fileName is False: self.fileDialog = FileDialog(self, "Save HTML as...", self.manager.getCurrentDir().name()) # self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.exportHtml) return if fileName[-5:] != ".html": fileName += ".html" # doc = self.ui.output.document().toHtml('utf-8') # for e in self.displayedEntries: # if e.has_key('tracebackHtml'): # doc = re.sub(r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>'%(str(e['id']), str(e['id'])), e['tracebackHtml'], doc) global pageTemplate doc = pageTemplate for e in self.displayedEntries: doc += self.cache[id(e)] for e in self.displayedEntries: if e.has_key("tracebackHtml"): doc = re.sub( r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>' % (str(e["id"]), str(e["id"])), e["tracebackHtml"], doc, ) # doc = self.ui.logView.page().currentFrame().toHtml() f = open(fileName, "w") f.write(doc.encode("utf-8")) f.close()
def __init__(self, manager, name, config): Module.__init__(self, manager, name, config) #self.dm = self.manager.dataManager self.dm = getDataManager() self.win = Window() mp = os.path.dirname(__file__) self.win.setWindowIcon(QtGui.QIcon(os.path.join(mp, 'icon.png'))) self.win.dm = self ## so embedded widgets can find the module easily self.ui = Ui_MainWindow() self.ui.setupUi(self.win) self.ui.analysisWidget = FileAnalysisView.FileAnalysisView(self.ui.analysisTab, self) self.ui.analysisTab.layout().addWidget(self.ui.analysisWidget) self.ui.logWidget = FileLogView.FileLogView(self.ui.logTab, self) self.ui.logTab.layout().addWidget(self.ui.logWidget) self.win.show() w = self.ui.splitter.width() self.ui.splitter.setSizes([int(w*0.4), int(w*0.6)]) self.ui.logDock.hide() self.dialog = FileDialog() self.dialog.setFileMode(QtGui.QFileDialog.DirectoryOnly) self.ui.fileTreeWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) ## Load values into GUI #self.model = DMModel(self.manager.getBaseDir()) #self.ui.fileTreeView.setModel(self.model) self.baseDirChanged() self.currentDirChanged() self.selFile = None self.updateNewFolderList() ## Make all connections needed self.ui.selectDirBtn.clicked.connect(self.showFileDialog) self.ui.setCurrentDirBtn.clicked.connect(self.setCurrentClicked) self.dialog.filesSelected.connect(self.setBaseDir) self.manager.sigBaseDirChanged.connect(self.baseDirChanged) self.manager.sigCurrentDirChanged.connect(self.currentDirChanged) self.manager.sigConfigChanged.connect(self.updateNewFolderList) self.manager.sigLogDirChanged.connect(self.updateLogDir) self.ui.setLogDirBtn.clicked.connect(self.setLogDir) self.ui.newFolderList.currentIndexChanged.connect(self.newFolder) self.ui.fileTreeWidget.itemSelectionChanged.connect(self.fileSelectionChanged) #self.ui.logEntryText.returnPressed.connect(self.logEntry) self.ui.fileDisplayTabs.currentChanged.connect(self.tabChanged) self.win.sigClosed.connect(self.quit) self.ui.analysisWidget.sigDbChanged.connect(self.analysisDbChanged) #self.logBtn = LogButton('Log') self.win.setStatusBar(StatusBar())
class FileAnalysisView(QtGui.QWidget): sigDbChanged = QtCore.Signal() def __init__(self, parent, mod): QtGui.QWidget.__init__(self, parent) self.ui = Ui_Form() self.ui.setupUi(self) self.man = acq4.Manager.getManager() self.mod = mod self.dbFile = None self.db = None self.mods = [] self.currentModel = None self.populateModuleList() self.populateModelList() stateFile = os.path.join('modules', self.mod.name + '_db_file_list') files = self.mod.manager.readConfigFile(stateFile).get('db_file_list', []) self.ui.databaseCombo.addItem('') for f in files: self.ui.databaseCombo.addItem(f) self.ui.openDbBtn.clicked.connect(self.openDbClicked) self.ui.createDbBtn.clicked.connect(self.createDbClicked) self.ui.loadModuleBtn.clicked.connect(self.loadModule) self.ui.refreshDbBtn.clicked.connect(self.refreshDb) self.ui.dataModelCombo.currentIndexChanged.connect(self.loadModel) self.ui.analysisModuleList.currentItemChanged.connect(self.showModuleDescription) self.ui.analysisModuleList.itemDoubleClicked.connect(self.loadModule) self.ui.databaseCombo.currentIndexChanged.connect(self.dbComboChanged) def openDbClicked(self): bd = self.man.getBaseDir() if bd is None: bd = "" else: bd = bd.name() self.fileDialog = FileDialog(self, "Select Database File", bd, "SQLite Database (*.sqlite *.sql);;All Files (*.*)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.openDb) def openDb(self, fileName): #fn = str(QtGui.QFileDialog.getOpenFileName(self, "Select Database File", self.man.getBaseDir().name(), "SQLite Database (*.sqlite)")) fileName = str(fileName) if fileName == '': return #if not fileName[-7:] == '.sqlite' and '.' not in fileName: # fileName =+ '.sqlite' self.ui.databaseCombo.blockSignals(True) try: ## put fileName at the top of the list, write to disk files = [self.ui.databaseCombo.itemText(i) for i in range(self.ui.databaseCombo.count())] files.remove('') if fileName in files: files.remove(fileName) files = [fileName] + files self.ui.databaseCombo.clear() self.ui.databaseCombo.addItem('') for f in files: self.ui.databaseCombo.addItem(f) stateFile = os.path.join('modules', self.mod.name + '_db_file_list') self.mod.manager.writeConfigFile({'db_file_list': files}, stateFile) self.ui.databaseCombo.setCurrentIndex(1) finally: self.ui.databaseCombo.blockSignals(False) self.dbFile = fileName self.db = database.AnalysisDatabase(self.dbFile, dataModel=self.currentModel) self.sigDbChanged.emit() def dbComboChanged(self): fn = self.ui.databaseCombo.currentText() if fn == '': return if not os.path.exists(fn): raise Exception("Database file does not exist: %s" % fn) self.openDb(fn) def quit(self): if self.db is not None: self.db.close() def createDbClicked(self): bd = self.man.getBaseDir() if bd is None: raise Exception("Must select a base directory before creating database.") self.fileDialog = FileDialog(self, "Create Database File", bd.name(), "SQLite Database (*.sqlite *.sql);;All Files (*.*)") #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.setOption(QtGui.QFileDialog.DontConfirmOverwrite) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.createDb) def createDb(self, fileName): #fn = str(QtGui.QFileDialog.getSaveFileName(self, "Create Database File", self.man.getBaseDir().name(), "SQLite Database (*.sqlite)", None, QtGui.QFileDialog.DontConfirmOverwrite)) fileName = str(fileName) if fileName is '': return self.dbFile = fileName self.db = database.AnalysisDatabase(self.dbFile, dataModel=self.currentModel, baseDir=self.man.getBaseDir()) self.ui.databaseCombo.blockSignals(True) try: self.ui.databaseCombo.addItem(fileName) self.ui.databaseCombo.setCurrentIndex(self.ui.databaseCombo.count()-1) finally: self.ui.databaseCombo.blockSignals(False) self.sigDbChanged.emit() def refreshDb(self): if self.db is None: return self.db._readTableList() def addFileClicked(self): cf = self.mod.selectedFile() self.db.addDir(cf) def populateModuleList(self): for m in analysis.listModules(): self.ui.analysisModuleList.addItem(m) def loadModule(self): mod = self.ui.analysisModuleList.currentItem() if mod is None: return modName = str(mod.text()) #if self.ui.analysisCombo.currentIndex() == 0: #return #modName = str(self.ui.analysisCombo.currentText()) #self.ui.analysisCombo.setCurrentIndex(0) mod = AnalysisHost.AnalysisHost(dataManager=self.mod, dataModel=self.currentModel, module=modName) self.mods.append(mod) self.man.modules[modName] = mod def populateModelList(self): self.ui.dataModelCombo.clear() self.ui.dataModelCombo.addItem('Load...') mods = models.listModels() for m in mods: self.ui.dataModelCombo.addItem(m) if len(mods) == 1: self.ui.dataModelCombo.setCurrentIndex(1) self.loadModel() def loadModel(self): if self.ui.dataModelCombo.currentIndex() == 0: return modName = str(self.ui.dataModelCombo.currentText()) self.currentModel = models.loadModel(modName) acq4.Manager.getManager().dataModel = self.currentModel ## make model globally available if self.db is not None: self.db.setDataModel(self.currentModel) def currentDatabase(self): return self.db def currentDataModel(self): return self.currentModel def showModuleDescription(self): mod = self.ui.analysisModuleList.currentItem() if mod is None: return modName = str(mod.text()) cls = analysis.getModuleClass(modName) doc = cls.__doc__ self.ui.modDescriptionText.setPlainText(doc)
class DataManager(Module): moduleDisplayName = "Data Manager" moduleCategory = "Acquisition" sigAnalysisDbChanged = Qt.Signal() def __init__(self, manager, name, config): Module.__init__(self, manager, name, config) self.dm = getDataManager() self.win = Window() mp = os.path.dirname(__file__) self.win.setWindowIcon(Qt.QIcon(os.path.join(mp, 'icon.png'))) self.win.dm = self ## so embedded widgets can find the module easily self.ui = Ui_MainWindow() self.ui.setupUi(self.win) self.ui.analysisWidget = FileAnalysisView.FileAnalysisView( self.ui.analysisTab, self) self.ui.analysisTab.layout().addWidget(self.ui.analysisWidget) self.ui.logWidget = FileLogView.FileLogView(self.ui.logTab, self) self.ui.logTab.layout().addWidget(self.ui.logWidget) self.win.show() w = self.ui.splitter.width() self.ui.splitter.setSizes([int(w * 0.4), int(w * 0.6)]) self.ui.logDock.hide() self.dialog = None self.ui.fileTreeWidget.setSelectionMode( Qt.QAbstractItemView.ExtendedSelection) self.baseDirChanged() self.currentDirChanged() self.selFile = None self.updateNewFolderList() ## Make all connections needed self.ui.selectDirBtn.clicked.connect(self.showFileDialog) self.ui.setCurrentDirBtn.clicked.connect(self.setCurrentClicked) self.manager.sigBaseDirChanged.connect(self.baseDirChanged) self.manager.sigCurrentDirChanged.connect(self.currentDirChanged) self.manager.sigConfigChanged.connect(self.updateNewFolderList) self.manager.sigLogDirChanged.connect(self.updateLogDir) self.ui.setLogDirBtn.clicked.connect(self.setLogDir) self.ui.newFolderList.currentIndexChanged.connect(self.newFolder) self.ui.fileTreeWidget.itemSelectionChanged.connect( self.fileSelectionChanged) self.ui.fileDisplayTabs.currentChanged.connect(self.tabChanged) self.win.sigClosed.connect(self.quit) self.ui.analysisWidget.sigDbChanged.connect(self.analysisDbChanged) self.ui.baseDirText.editingFinished.connect(self.baseDirTextChanged) self.win.setStatusBar(StatusBar()) def updateNewFolderList(self): self.ui.newFolderList.clear() conf = self.manager.config['folderTypes'] #print "folderTypes:", self.manager.config['folderTypes'].keys() self.ui.newFolderList.clear() self.ui.newFolderList.addItems(['New...', 'Folder'] + list(conf.keys())) def baseDirChanged(self): dh = self.manager.getBaseDir() self.baseDir = dh if dh is None: self.ui.baseDirText.setText('') else: self.ui.baseDirText.setText(dh.name()) self.ui.fileTreeWidget.setBaseDirHandle(dh) def loadLog(self, *args, **kwargs): pass def selectFile(self, path): if isinstance(path, six.string_types): path = getHandle(path) self.ui.fileTreeWidget.select(path) def setLogDir(self): d = self.selectedFile() if not isinstance(d, DirHandle): d = d.parent() self.manager.setLogDir(d) def updateLogDir(self, d): self.ui.logDirText.setText(d.name(relativeTo=self.baseDir)) def setCurrentClicked(self): #print "click" handle = self.selectedFile() if handle is None: #print "no selection" return if not handle.isDir(): handle = handle.parent() self.manager.setCurrentDir(handle) def currentDirChanged(self, name=None, change=None, args=()): if change in [None, 'moved', 'renamed', 'parent']: try: newDir = self.manager.getCurrentDir() except: newDir = None dirName = "" else: dirName = newDir.name(relativeTo=self.baseDir) self.ui.currentDirText.setText(str(dirName)) self.ui.fileTreeWidget.setCurrentDir(newDir) elif change == 'log': self.updateLogView(*args) if change == None: try: newDir = self.manager.getCurrentDir() except: newDir = None else: self.loadLog(newDir, self.ui.logView) def showFileDialog(self): bd = self.manager.getBaseDir() if self.dialog is None: self.dialog = FileDialog() self.dialog.setFileMode(Qt.QFileDialog.DirectoryOnly) self.dialog.filesSelected.connect(self.setBaseDir) if bd is not None: self.dialog.setDirectory(bd.name()) self.dialog.show() def baseDirTextChanged(self): path = str(self.ui.baseDirText.text()) if not os.path.isdir(path): raise ValueError("Path %s does not exist" % path) self.setBaseDir(path) def setBaseDir(self, dirName): if isinstance(dirName, list): if len(dirName) == 1: dirName = dirName[0] else: raise Exception("Caught. Please to be examined: %s" % str(dirName)) if dirName is None: return if os.path.isdir(dirName): self.manager.setBaseDir(dirName) else: raise Exception("Storage directory is invalid") def selectedFile(self): """Return the currently selected file""" items = self.ui.fileTreeWidget.selectedItems() if len(items) > 0: return items[0].handle else: return None def newFolder(self): if self.ui.newFolderList.currentIndex() < 1: return ftype = str(self.ui.newFolderList.currentText()) self.ui.newFolderList.setCurrentIndex(0) cdir = self.manager.getCurrentDir() if not cdir.isManaged(): cdir.createIndex() if ftype == 'Folder': nd = cdir.mkdir('NewFolder', autoIncrement=True) #item = self.model.handleIndex(nd) self.ui.fileTreeWidget.editItem(nd) else: spec = self.manager.config['folderTypes'][ftype] name = time.strftime(spec['name']) ## Determine where to put the new directory parent = cdir try: checkDir = cdir for i in range(5): if not checkDir.isManaged(): break inf = checkDir.info() if 'dirType' in inf and inf['dirType'] == ftype: parent = checkDir.parent() break #else: #print "dir no match:", spec, inf checkDir = checkDir.parent() except: printExc( "Error while deciding where to put new folder (using currentDir by default)" ) ## make nd = parent.mkdir(name, autoIncrement=True) ## Add meta-info info = {'dirType': ftype} if spec.get('experimentalUnit', False): info['expUnit'] = True nd.setInfo(info) self.ui.fileTreeWidget.refresh( parent ) ## fileTreeWidget waits a while before updating; force it to refresh immediately. self.ui.fileTreeWidget.select(nd) logMsg("Created new folder: %s" % nd.name(relativeTo=self.baseDir), msgType='status', importance=7) self.manager.setCurrentDir(nd) def fileSelectionChanged(self): #print "file selection changed" if self.selFile is not None: try: self.selFile.sigChanged.disconnect(self.selectedFileAltered) except TypeError: pass fh = self.selectedFile() self.manager.currentFile = fh ## Make this really easy to pick up from an interactive prompt. self.loadFile(fh) self.selFile = fh if fh is not None: self.selFile.sigChanged.connect(self.selectedFileAltered) def loadFile(self, fh): if fh is None: self.ui.fileInfo.setCurrentFile(None) self.ui.dataViewWidget.setCurrentFile(None) self.ui.logWidget.selectedFileChanged(None) self.ui.fileNameLabel.setText('') else: self.ui.fileNameLabel.setText(fh.name(relativeTo=self.baseDir)) self.tabChanged() def tabChanged(self, n=None): if n is None: n = self.ui.fileDisplayTabs.currentIndex() fh = self.selectedFile() if n == 0: self.ui.fileInfo.setCurrentFile(fh) elif n == 1: self.ui.logWidget.selectedFileChanged(fh) elif n == 2: self.ui.dataViewWidget.setCurrentFile(fh) def selectedFileAltered(self, name, change, args): if change in ['parent', 'renamed', 'moved' ] and self.selFile is not None: self.ui.fileTreeWidget.select( self.selFile) ## re-select file if it has moved. self.ui.fileNameLabel.setText( self.selFile.name(relativeTo=self.baseDir)) def quit(self): ## Silly: needed to prevent lockup on some systems. #print " module quitting.." self.ui.fileTreeWidget.quit() self.ui.analysisWidget.quit() #print " deleted dialog, calling superclass quit.." Module.quit(self) #print " module quit done" #print backtrace() def currentDatabase(self): return self.ui.analysisWidget.currentDatabase() def dataModel(self): return self.ui.analysisWidget.currentDataModel() def analysisDbChanged(self): self.sigAnalysisDbChanged.emit()
class DataManager(Module): sigAnalysisDbChanged = QtCore.Signal() def __init__(self, manager, name, config): Module.__init__(self, manager, name, config) #self.dm = self.manager.dataManager self.dm = getDataManager() self.win = Window() mp = os.path.dirname(__file__) self.win.setWindowIcon(QtGui.QIcon(os.path.join(mp, 'icon.png'))) self.win.dm = self ## so embedded widgets can find the module easily self.ui = Ui_MainWindow() self.ui.setupUi(self.win) self.ui.analysisWidget = FileAnalysisView.FileAnalysisView( self.ui.analysisTab, self) self.ui.analysisTab.layout().addWidget(self.ui.analysisWidget) self.ui.logWidget = FileLogView.FileLogView(self.ui.logTab, self) self.ui.logTab.layout().addWidget(self.ui.logWidget) self.win.show() w = self.ui.splitter.width() self.ui.splitter.setSizes([int(w * 0.4), int(w * 0.6)]) self.ui.logDock.hide() self.dialog = FileDialog() self.dialog.setFileMode(QtGui.QFileDialog.DirectoryOnly) self.ui.fileTreeWidget.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) ## Load values into GUI #self.model = DMModel(self.manager.getBaseDir()) #self.ui.fileTreeView.setModel(self.model) self.baseDirChanged() self.currentDirChanged() self.selFile = None self.updateNewFolderList() ## Make all connections needed self.ui.selectDirBtn.clicked.connect(self.showFileDialog) self.ui.setCurrentDirBtn.clicked.connect(self.setCurrentClicked) self.dialog.filesSelected.connect(self.setBaseDir) self.manager.sigBaseDirChanged.connect(self.baseDirChanged) self.manager.sigCurrentDirChanged.connect(self.currentDirChanged) self.manager.sigConfigChanged.connect(self.updateNewFolderList) self.manager.sigLogDirChanged.connect(self.updateLogDir) self.ui.setLogDirBtn.clicked.connect(self.setLogDir) self.ui.newFolderList.currentIndexChanged.connect(self.newFolder) self.ui.fileTreeWidget.itemSelectionChanged.connect( self.fileSelectionChanged) #self.ui.logEntryText.returnPressed.connect(self.logEntry) self.ui.fileDisplayTabs.currentChanged.connect(self.tabChanged) self.win.sigClosed.connect(self.quit) self.ui.analysisWidget.sigDbChanged.connect(self.analysisDbChanged) #self.logBtn = LogButton('Log') self.win.setStatusBar(StatusBar()) #self.win.statusBar().addPermanentWidget(self.logBtn) #self.win.statusBar().setFixedHeight(25) #self.win.statusBar().layout().setSpacing(0) #def hasInterface(self, interface): #return interface in ['DataSource'] def updateNewFolderList(self): self.ui.newFolderList.clear() conf = self.manager.config['folderTypes'] #print "folderTypes:", self.manager.config['folderTypes'].keys() self.ui.newFolderList.clear() self.ui.newFolderList.addItems(['New...', 'Folder'] + conf.keys()) def baseDirChanged(self): dh = self.manager.getBaseDir() self.baseDir = dh if dh is None: self.ui.baseDirText.setText('') else: self.ui.baseDirText.setText(dh.name()) self.ui.fileTreeWidget.setBaseDirHandle(dh) def loadLog(self, *args, **kwargs): pass def setLogDir(self): d = self.selectedFile() if not isinstance(d, DirHandle): d = d.parent() self.manager.setLogDir(d) def updateLogDir(self, d): self.ui.logDirText.setText(d.name(relativeTo=self.baseDir)) def setCurrentClicked(self): #print "click" handle = self.selectedFile() if handle is None: #print "no selection" return if not handle.isDir(): handle = handle.parent() #dh = self.manager.dirHandle(newDir) self.manager.setCurrentDir(handle) def currentDirChanged(self, name=None, change=None, args=()): if change in [None, 'moved', 'renamed', 'parent']: try: newDir = self.manager.getCurrentDir() except: newDir = None dirName = "" else: dirName = newDir.name(relativeTo=self.baseDir) self.ui.currentDirText.setText(str(dirName)) self.ui.fileTreeWidget.setCurrentDir(newDir) elif change == 'log': self.updateLogView(*args) if change == None: try: newDir = self.manager.getCurrentDir() except: newDir = None else: self.loadLog(newDir, self.ui.logView) #def loadLog(self, dirHandle, widget, recursive=0): #widget.clear() #log = dirHandle.readLog(recursive) #for line in self.logRender(log): #widget.append(line) def showFileDialog(self): bd = self.manager.getBaseDir() if bd is not None: self.dialog.setDirectory(bd.name()) self.dialog.show() def setBaseDir(self, dirName): if isinstance(dirName, list): if len(dirName) == 1: dirName = dirName[0] else: raise Exception("Caught. Please to be examined: %s" % str(dirName)) #if dirName is None: #dirName = QtGui.QFileDialog.getExistingDirectory() #if type(dirName) is QtCore.QStringList: # dirName = str(dirName[0]) #raise Exception("Caught. Please to be examined.") #if type(dirName) is QtCore.QStringList: #dirName = str(dirName[0]) #elif type(dirName) is QtCore.QString: #dirName = str(dirName) if dirName is None: return if os.path.isdir(dirName): self.manager.setBaseDir(dirName) else: raise Exception("Storage directory is invalid") def selectedFile(self): """Return the currently selected file""" items = self.ui.fileTreeWidget.selectedItems() if len(items) > 0: return items[0].handle else: return None #sel = list(self.ui.fileTreeWidget.selectedIndexes()) #if len(sel) == 0: # return None #if len(sel) == 1: # index = sel[0] #else: # raise Exception("Error - multiple items selected") ##print "index:", index.internalPointer() #if index.internalPointer() is None: # return None #return self.model.handle(index) def newFolder(self): if self.ui.newFolderList.currentIndex() < 1: return ftype = str(self.ui.newFolderList.currentText()) self.ui.newFolderList.setCurrentIndex(0) cdir = self.manager.getCurrentDir() if not cdir.isManaged(): cdir.createIndex() if ftype == 'Folder': nd = cdir.mkdir('NewFolder', autoIncrement=True) #item = self.model.handleIndex(nd) self.ui.fileTreeWidget.editItem(nd) else: spec = self.manager.config['folderTypes'][ftype] name = time.strftime(spec['name']) ## Determine where to put the new directory parent = cdir try: checkDir = cdir for i in range(5): if not checkDir.isManaged(): break inf = checkDir.info() if 'dirType' in inf and inf['dirType'] == ftype: parent = checkDir.parent() break #else: #print "dir no match:", spec, inf checkDir = checkDir.parent() except: printExc( "Error while deciding where to put new folder (using currentDir by default)" ) ## make nd = parent.mkdir(name, autoIncrement=True) ## Add meta-info info = {'dirType': ftype} if spec.get('experimentalUnit', False): info['expUnit'] = True nd.setInfo(info) ## set display to info #self.showFileInfo(nd) #index = self.model.handleIndex(nd) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Clear) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Select) self.ui.fileTreeWidget.refresh( parent ) ## fileTreeWidget waits a while before updating; force it to refresh immediately. self.ui.fileTreeWidget.select(nd) ##self.ui.fileInfo.setCurrentFile(nd) logMsg("Created new folder: %s" % nd.name(relativeTo=self.baseDir), msgType='status', importance=7) self.manager.setCurrentDir(nd) def fileSelectionChanged(self): #print "file selection changed" if self.selFile is not None: #QtCore.QObject.disconnect(self.selFile, QtCore.SIGNAL('changed'), self.selectedFileAltered) try: self.selFile.sigChanged.disconnect(self.selectedFileAltered) except TypeError: pass fh = self.selectedFile() self.manager.currentFile = fh ## Make this really easy to pick up from an interactive prompt. self.loadFile(fh) self.selFile = fh if fh is not None: #QtCore.QObject.connect(self.selFile, QtCore.SIGNAL('changed'), self.selectedFileAltered) self.selFile.sigChanged.connect(self.selectedFileAltered) def loadFile(self, fh): #self.ui.selectedLogView.clear() if fh is None: self.ui.fileInfo.setCurrentFile(None) self.ui.dataViewWidget.setCurrentFile(None) self.ui.logWidget.selectedFileChanged(None) self.ui.fileNameLabel.setText('') else: #self.ui.fileInfo.setCurrentFile(fh) #self.ui.dataViewWidget.setCurrentFile(fh) self.ui.fileNameLabel.setText(fh.name(relativeTo=self.baseDir)) #if fh.isDir(): #self.loadLog(fh, self.ui.selectedLogView, recursive=3) self.tabChanged() def tabChanged(self, n=None): if n is None: n = self.ui.fileDisplayTabs.currentIndex() fh = self.selectedFile() if n == 0: self.ui.fileInfo.setCurrentFile(fh) elif n == 1: self.ui.logWidget.selectedFileChanged(fh) #if fh.isDir(): #self.loadLog(fh, self.ui.selectedLogView, recursive=3) elif n == 2: self.ui.dataViewWidget.setCurrentFile(fh) def selectedFileAltered(self, name, change, args): if change in ['parent', 'renamed', 'moved' ] and self.selFile is not None: #index = self.model.handleIndex(self.selFile) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Clear) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Select) self.ui.fileTreeWidget.select( self.selFile) ## re-select file if it has moved. self.ui.fileNameLabel.setText( self.selFile.name(relativeTo=self.baseDir)) #self.fileSelectionChanged() #def logEntry(self): #text = str(self.ui.logEntryText.text()) #cd = self.manager.getCurrentDir() #self.ui.logEntryText.setText('') #if text == '' or cd is None: #return #cd.logMsg(text, {'source': 'user'}) #def updateLogView(self, *args): #msg = args[0] #self.ui.logView.append(self.logRender(msg)) ##print "new log msg" #def logRender(self, log): #returnList = True #if type(log) is dict: #log = [log] #returnList = False #elif type(log) is not list: #raise Exception('logRender requires dict or list of dicts as argument') #lines = [] #for msg in log: #t = time.strftime('%Y.%m.%d %H:%M:%S', time.localtime(msg['__timestamp__'])) #style = 'color: #000; font-style: normal' #sourceStyles = { #'user': '******' #} #if 'source' in msg and msg['source'] in sourceStyles: #style = sourceStyles[msg['source']] #parts = ["<span style='color: #888'>[%s]</span>" % t] #if 'subdir' in msg: #parts.append(msg['subdir']) #parts.append("<span style='%s'>%s</span>" % (style, msg['__message__'])) #lines.append(' '.join(parts)) #if returnList: #return lines #else: #return lines[0] def quit(self): ## Silly: needed to prevent lockup on some systems. #print " module quitting.." self.ui.fileTreeWidget.quit() self.ui.analysisWidget.quit() #sip.delete(self.dialog) #print " deleted dialog, calling superclass quit.." Module.quit(self) #print " module quit done" #print backtrace() def currentDatabase(self): return self.ui.analysisWidget.currentDatabase() def dataModel(self): return self.ui.analysisWidget.currentDataModel() def analysisDbChanged(self): self.sigAnalysisDbChanged.emit()
class LogWidget(QtGui.QWidget): sigDisplayEntry = QtCore.Signal(object) ## for thread-safetyness sigAddEntry = QtCore.Signal(object) ## for thread-safetyness sigScrollToAnchor = QtCore.Signal(object) # for internal use. def __init__(self, parent, manager): QtGui.QWidget.__init__(self, parent) self.ui = LogWidgetTemplate.Ui_Form() self.manager = manager self.ui.setupUi(self) # self.ui.input.hide() self.ui.filterTree.topLevelItem(1).setExpanded(True) self.entries = [] ## stores all log entries in memory self.cache = {} ## for storing html strings of entries that have already been processed self.displayedEntries = [] # self.currentEntries = None ## recordArray that stores currently displayed entries -- so that if filters get more restrictive we can just refilter this list instead of filtering everything self.typeFilters = [] self.importanceFilter = 0 self.dirFilter = False self.entryArrayBuffer = np.zeros( 1000, dtype=[ ### a record array for quick filtering of entries ("index", "int32"), ("importance", "int32"), ("msgType", "|S10"), ("directory", "|S100"), ("entryId", "int32"), ], ) self.entryArray = self.entryArrayBuffer[:0] self.filtersChanged() self.sigDisplayEntry.connect(self.displayEntry, QtCore.Qt.QueuedConnection) self.sigAddEntry.connect(self.addEntry, QtCore.Qt.QueuedConnection) self.ui.exportHtmlBtn.clicked.connect(self.exportHtml) self.ui.filterTree.itemChanged.connect(self.setCheckStates) self.ui.importanceSlider.valueChanged.connect(self.filtersChanged) # self.ui.logView.linkClicked.connect(self.linkClicked) self.ui.output.anchorClicked.connect(self.linkClicked) self.sigScrollToAnchor.connect(self.scrollToAnchor, QtCore.Qt.QueuedConnection) # page = self.ui.logView.page() # page.setLinkDelegationPolicy(page.DelegateAllLinks) def loadFile(self, f): """Load the file, f. f must be able to be read by configfile.py""" log = configfile.readConfigFile(f) self.entries = [] self.entryArrayBuffer = np.zeros( len(log), dtype=[ ("index", "int32"), ("importance", "int32"), ("msgType", "|S10"), ("directory", "|S100"), ("entryId", "int32"), ], ) self.entryArray = self.entryArrayBuffer[:] i = 0 for k, v in log.iteritems(): v["id"] = k[9:] ## record unique ID to facilitate HTML generation (javascript needs this ID) self.entries.append(v) self.entryArray[i] = np.array( [ ( i, v.get("importance", 5), v.get("msgType", "status"), v.get("currentDir", ""), v.get("entryId", v["id"]), ) ], dtype=[ ("index", "int32"), ("importance", "int32"), ("msgType", "|S10"), ("directory", "|S100"), ("entryId", "int32"), ], ) i += 1 self.filterEntries() ## puts all entries through current filters and displays the ones that pass def addEntry(self, entry): ## All incoming messages begin here ## for thread-safetyness: isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if not isGuiThread: self.sigAddEntry.emit(entry) return self.entries.append(entry) i = len(self.entryArray) entryDir = entry.get("currentDir", None) if entryDir is None: entryDir = "" arr = np.array( [(i, entry["importance"], entry["msgType"], entryDir, entry["id"])], dtype=[ ("index", "int32"), ("importance", "int32"), ("msgType", "|S10"), ("directory", "|S100"), ("entryId", "int32"), ], ) ## make more room if needed if len(self.entryArrayBuffer) == len(self.entryArray): newArray = np.empty(len(self.entryArrayBuffer) + 1000, self.entryArrayBuffer.dtype) newArray[: len(self.entryArray)] = self.entryArray self.entryArrayBuffer = newArray self.entryArray = self.entryArrayBuffer[: len(self.entryArray) + 1] # self.entryArray[i] = [(i, entry['importance'], entry['msgType'], entry['currentDir'])] self.entryArray[i] = arr self.checkDisplay(entry) ## displays the entry if it passes the current filters # np.append(self.entryArray, np.array(i, [[i, entry['importance'], entry['msgType'], entry['currentDir']]]), dtype = [('index', int), ('importance', int), ('msgType', str), ('directory', str)]) def setCheckStates(self, item, column): if item == self.ui.filterTree.topLevelItem(1): if item.checkState(0): for i in range(item.childCount()): item.child(i).setCheckState(0, QtCore.Qt.Checked) elif item.parent() == self.ui.filterTree.topLevelItem(1): if not item.checkState(0): self.ui.filterTree.topLevelItem(1).setCheckState(0, QtCore.Qt.Unchecked) self.filtersChanged() def filtersChanged(self): ### Update self.typeFilters, self.importanceFilter, and self.dirFilter to reflect changes. tree = self.ui.filterTree self.typeFilters = [] for i in range(tree.topLevelItem(1).childCount()): child = tree.topLevelItem(1).child(i) if tree.topLevelItem(1).checkState(0) or child.checkState(0): text = child.text(0) self.typeFilters.append(unicode(text)) self.importanceFilter = self.ui.importanceSlider.value() self.updateDirFilter() # self.dirFilter = self.manager.getDirOfSelectedFile().name() # else: # self.dirFilter = False self.filterEntries() def updateDirFilter(self, dh=None): if self.ui.filterTree.topLevelItem(0).checkState(0): if dh == None: self.dirFilter = self.manager.getDirOfSelectedFile().name() else: self.dirFilter = dh.name() else: self.dirFilter = False def filterEntries(self): """Runs each entry in self.entries through the filters and displays if it makes it through.""" ### make self.entries a record array, then filtering will be much faster (to OR true/false arrays, + them) typeMask = self.entryArray["msgType"] == "" for t in self.typeFilters: typeMask += self.entryArray["msgType"] == t mask = (self.entryArray["importance"] > self.importanceFilter) * typeMask if self.dirFilter != False: d = np.ascontiguousarray(self.entryArray["directory"]) j = len(self.dirFilter) i = len(d) d = d.view(np.byte).reshape(i, 100)[:, :j] d = d.reshape(i * j).view("|S%d" % j) mask *= d == self.dirFilter self.ui.output.clear() global Stylesheet self.ui.output.document().setDefaultStyleSheet(Stylesheet) # global pageTemplate # self.ui.logView.setHtml(pageTemplate) indices = list(self.entryArray[mask]["index"]) inds = indices # if self.dirFilter != False: # j = len(self.dirFilter) # for i, n in inds: # if not self.entries[n]['currentDir'][:j] == self.dirFilter: # indices.pop(i) self.displayEntry([self.entries[i] for i in indices]) def checkDisplay(self, entry): ### checks whether entry passes the current filters and displays it if it does. if entry["msgType"] not in self.typeFilters: return elif entry["importance"] < self.importanceFilter: return elif self.dirFilter is not False: if entry["currentDir"][: len(self.dirFilter)] != self.dirFilter: return else: self.displayEntry([entry]) def displayEntry(self, entries): ## entries should be a list of log entries ## for thread-safetyness: isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if not isGuiThread: self.sigDisplayEntry.emit(entries) return for entry in entries: if not self.cache.has_key(id(entry)): self.cache[id(entry)] = self.generateEntryHtml(entry) ## determine message color: # if entry['msgType'] == 'status': # color = 'green' # elif entry['msgType'] == 'user': # color = 'blue' # elif entry['msgType'] == 'error': # color = 'red' # elif entry['msgType'] == 'warning': # color = '#DD4400' ## orange # else: # color = 'black' # if entry.has_key('exception') or entry.has_key('docs') or entry.has_key('reasons'): ##self.displayComplexMessage(entry, color) # self.displayComplexMessage(entry) # else: # self.displayText(entry['message'], entry, color, timeStamp=entry['timestamp']) # for x in self.cache[id(entry)]: # self.ui.output.appendHtml(x) html = self.cache[id(entry)] # frame = self.ui.logView.page().currentFrame() # isMax = frame.scrollBarValue(QtCore.Qt.Vertical) == frame.scrollBarMaximum(QtCore.Qt.Vertical) sb = self.ui.output.verticalScrollBar() isMax = sb.value() == sb.maximum() # frame.findFirstElement('body').appendInside(html) self.ui.output.append(html) self.displayedEntries.append(entry) if isMax: ## can't scroll to end until the web frame has processed the html change # frame.setScrollBarValue(QtCore.Qt.Vertical, frame.scrollBarMaximum(QtCore.Qt.Vertical)) ## Calling processEvents anywhere inside an error handler is forbidden ## because this can lead to Qt complaining about paint() recursion. # QtGui.QApplication.processEvents() # self.ui.output.scrollToAnchor(str(entry['id'])) self.sigScrollToAnchor.emit(str(entry["id"])) ## queued connection # self.ui.logView.update() def scrollToAnchor(self, anchor): self.ui.output.scrollToAnchor(anchor) def generateEntryHtml(self, entry): msg = self.cleanText(entry["message"]) reasons = "" docs = "" exc = "" if entry.has_key("reasons"): reasons = self.formatReasonStrForHTML(entry["reasons"]) if entry.has_key("docs"): docs = self.formatDocsStrForHTML(entry["docs"]) if entry.get("exception", None) is not None: exc = self.formatExceptionForHTML(entry, entryId=entry["id"]) extra = reasons + docs + exc if extra != "": # extra = "<div class='logExtra'>" + extra + "</div>" extra = "<table class='logExtra'><tr><td>" + extra + "</td></tr></table>" # return """ # <div class='entry'> # <div class='%s'> # <span class='timestamp'>%s</span> # <span class='message'>%s</span> #%s # </div> # </div> # """ % (entry['msgType'], entry['timestamp'], msg, extra) return """ <a name="%s"/><table class='entry'><tr><td> <table class='%s'><tr><td> <span class='timestamp'>%s</span> <span class='message'>%s</span> %s </td></tr></table> </td></tr></table> """ % ( str(entry["id"]), entry["msgType"], entry["timestamp"], msg, extra, ) # if entry.has_key('exception') or entry.has_key('docs') or entry.has_key('reasons'): ##self.displayComplexMessage(entry, color) # return self.generateComplex(entry) # else: # return self.generateSimple(entry['message'], entry, color, timeStamp=entry['timestamp']) ##self.displayText(entry['message'], entry, color, timeStamp=entry['timestamp']) @staticmethod def cleanText(text): text = re.sub(r"&", "&", text) text = re.sub(r">", ">", text) text = re.sub(r"<", "<", text) text = re.sub(r"\n", "<br/>\n", text) return text # def displayText(self, msg, entry, colorStr='black', timeStamp=None, clean=True): # if clean: # msg = self.cleanText(msg) # if msg[-1:] == '\n': # msg = msg[:-1] # msg = '<br />'.join(msg.split('\n')) # if timeStamp is not None: # strn = '<b style="color:black"> %s </b> <span style="color:%s"> %s </span>' % (timeStamp, colorStr, msg) # else: # strn = '<span style="color:%s"> %s </span>' % (colorStr, msg) ##self.ui.output.appendHtml(strn) # self.cache[id(entry)].append(strn) # def displayComplexMessage(self, entry, color='black'): # self.displayText(entry['message'], entry, color, timeStamp = entry['timestamp'], clean=True) # if entry.has_key('reasons'): # reasons = self.formatReasonStrForHTML(entry['reasons']) # self.displayText(reasons, entry, 'black', clean=False) # if entry.has_key('docs'): # docs = self.formatDocsStrForHTML(entry['docs']) # self.displayText(docs, entry, 'black', clean=False) # if entry.get('exception', None) is not None: # self.displayException(entry['exception'], entry, 'black', tracebacks=entry.get('traceback', None)) def formatExceptionForHTML(self, entry, exception=None, count=1, entryId=None): ### Here, exception is a dict that holds the message, reasons, docs, traceback and oldExceptions (which are also dicts, with the same entries) ## the count and tracebacks keywords are for calling recursively if exception is None: exception = entry["exception"] # if tracebacks is None: # tracebacks = [] indent = 10 text = self.cleanText(exception["message"]) text = re.sub(r"^HelpfulException: ", "", text) # if exception.has_key('oldExc'): # self.displayText(" "*indent + str(count)+'. ' + text, entry, color, clean=False) # else: # self.displayText(" "*indent + str(count)+'. Original error: ' + text, entry, color, clean=False) messages = [text] # print "\n", messages, "\n" if exception.has_key("reasons"): reasons = self.formatReasonsStrForHTML(exception["reasons"]) text += reasons # self.displayText(reasons, entry, color, clean=False) if exception.has_key("docs"): docs = self.formatDocsStrForHTML(exception["docs"]) # self.displayText(docs, entry, color, clean=False) text += docs traceback = [self.formatTracebackForHTML(exception["traceback"], count)] text = [text] if exception.has_key("oldExc"): exc, tb, msgs = self.formatExceptionForHTML(entry, exception["oldExc"], count=count + 1) text.extend(exc) messages.extend(msgs) traceback.extend(tb) # else: # if len(tracebacks)==count+1: # n=0 # else: # n=1 # for i, tb in enumerate(tracebacks): # self.displayTraceback(tb, entry, number=i+n) if count == 1: exc = '<div class="exception"><ol>' + "\n".join(["<li>%s</li>" % ex for ex in text]) + "</ol></div>" tbStr = "\n".join( [ "<li><b>%s</b><br/><span class='traceback'>%s</span></li>" % (messages[i], tb) for i, tb in enumerate(traceback) ] ) # traceback = "<div class=\"traceback\" id=\"%s\"><ol>"%str(entryId) + tbStr + "</ol></div>" entry["tracebackHtml"] = tbStr # return exc + '<a href="#" onclick="showDiv(\'%s\')">Show traceback</a>'%str(entryId) + traceback return exc + '<a href="exc:%s">Show traceback %s</a>' % (str(entryId), str(entryId)) else: return text, traceback, messages def formatTracebackForHTML(self, tb, number): try: tb = [line for line in tb if not line.startswith("Traceback (most recent call last)")] except: print "\n" + str(tb) + "\n" raise return re.sub(" ", " ", ("").join(map(self.cleanText, tb)))[:-1] # tb = [self.cleanText(strip(x)) for x in tb] # lines = [] # prefix = '' # for l in ''.join(tb).split('\n'): # if l == '': # continue # if l[:9] == "Traceback": # prefix = ' ' + str(number) + '. ' # continue # spaceCount = 0 # while l[spaceCount] == ' ': # spaceCount += 1 # if prefix is not '': # spaceCount -= 1 # lines.append(" "*(spaceCount*4) + prefix + l) # prefix = '' # return '<div class="traceback">' + '<br />'.join(lines) + '</div>' # self.displayText('<br />'.join(lines), entry, color, clean=False) def formatReasonsStrForHTML(self, reasons): # indent = 6 reasonStr = "<table class='reasons'><tr><td>Possible reasons include:\n<ul>\n" for r in reasons: r = self.cleanText(r) reasonStr += "<li>" + r + "</li>\n" # reasonStr += " "*22 + chr(97+i) + ". " + r + "<br>" reasonStr += "</ul></td></tr></table>\n" return reasonStr def formatDocsStrForHTML(self, docs): # indent = 6 docStr = "<div class='docRefs'>Relevant documentation:\n<ul>\n" for d in docs: d = self.cleanText(d) docStr += '<li><a href="doc:%s">%s</a></li>\n' % (d, d) docStr += "</ul></div>\n" return docStr def exportHtml(self, fileName=False): # self.makeError1() if fileName is False: self.fileDialog = FileDialog(self, "Save HTML as...", self.manager.getCurrentDir().name()) # self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.exportHtml) return if fileName[-5:] != ".html": fileName += ".html" # doc = self.ui.output.document().toHtml('utf-8') # for e in self.displayedEntries: # if e.has_key('tracebackHtml'): # doc = re.sub(r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>'%(str(e['id']), str(e['id'])), e['tracebackHtml'], doc) global pageTemplate doc = pageTemplate for e in self.displayedEntries: doc += self.cache[id(e)] for e in self.displayedEntries: if e.has_key("tracebackHtml"): doc = re.sub( r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>' % (str(e["id"]), str(e["id"])), e["tracebackHtml"], doc, ) # doc = self.ui.logView.page().currentFrame().toHtml() f = open(fileName, "w") f.write(doc.encode("utf-8")) f.close() def makeError1(self): ### just for testing error logging try: self.makeError2() # print x except: t, exc, tb = sys.exc_info() # logExc(message="This button doesn't work", reasons='reason a, reason b', docs='documentation') # if isinstance(exc, HelpfulException): # exc.prependErr("Button doesn't work", (t,exc,tb), reasons = ["It's supposed to raise an error for testing purposes", "You're doing it wrong."]) # raise # else: printExc("This is the message sent to printExc.") # raise HelpfulException(message='This button does not work.', exc=(t, exc, tb), reasons=["It's supposed to raise an error for testing purposes", "You're doing it wrong."]) def makeError2(self): ### just for testing error logging try: print y except: t, exc, tb = sys.exc_info() raise HelpfulException( message="msg from makeError", exc=(t, exc, tb), reasons=["reason one", "reason 2"], docs=["what, you expect documentation?"], ) def linkClicked(self, url): url = url.toString() if url[:4] == "doc:": self.manager.showDocumentation(url[4:]) elif url[:4] == "exc:": cursor = self.ui.output.document().find("Show traceback %s" % url[4:]) try: tb = self.entries[int(url[4:]) - 1]["tracebackHtml"] except IndexError: try: tb = self.entries[self.entryArray[self.entryArray["entryId"] == (int(url[4:]))]["index"]][ "tracebackHtml" ] except: print "requested index %d, but only %d entries exist." % (int(url[4:]) - 1, len(self.entries)) raise cursor.insertHtml(tb) def clear(self): # self.ui.logView.setHtml("") self.ui.output.clear() self.displayedEntryies = []
class DataManager(Module): sigAnalysisDbChanged = QtCore.Signal() def __init__(self, manager, name, config): Module.__init__(self, manager, name, config) #self.dm = self.manager.dataManager self.dm = getDataManager() self.win = Window() mp = os.path.dirname(__file__) self.win.setWindowIcon(QtGui.QIcon(os.path.join(mp, 'icon.png'))) self.win.dm = self ## so embedded widgets can find the module easily self.ui = Ui_MainWindow() self.ui.setupUi(self.win) self.ui.analysisWidget = FileAnalysisView.FileAnalysisView(self.ui.analysisTab, self) self.ui.analysisTab.layout().addWidget(self.ui.analysisWidget) self.ui.logWidget = FileLogView.FileLogView(self.ui.logTab, self) self.ui.logTab.layout().addWidget(self.ui.logWidget) self.win.show() w = self.ui.splitter.width() self.ui.splitter.setSizes([int(w*0.4), int(w*0.6)]) self.ui.logDock.hide() self.dialog = FileDialog() self.dialog.setFileMode(QtGui.QFileDialog.DirectoryOnly) self.ui.fileTreeWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) ## Load values into GUI #self.model = DMModel(self.manager.getBaseDir()) #self.ui.fileTreeView.setModel(self.model) self.baseDirChanged() self.currentDirChanged() self.selFile = None self.updateNewFolderList() ## Make all connections needed self.ui.selectDirBtn.clicked.connect(self.showFileDialog) self.ui.setCurrentDirBtn.clicked.connect(self.setCurrentClicked) self.dialog.filesSelected.connect(self.setBaseDir) self.manager.sigBaseDirChanged.connect(self.baseDirChanged) self.manager.sigCurrentDirChanged.connect(self.currentDirChanged) self.manager.sigConfigChanged.connect(self.updateNewFolderList) self.manager.sigLogDirChanged.connect(self.updateLogDir) self.ui.setLogDirBtn.clicked.connect(self.setLogDir) self.ui.newFolderList.currentIndexChanged.connect(self.newFolder) self.ui.fileTreeWidget.itemSelectionChanged.connect(self.fileSelectionChanged) #self.ui.logEntryText.returnPressed.connect(self.logEntry) self.ui.fileDisplayTabs.currentChanged.connect(self.tabChanged) self.win.sigClosed.connect(self.quit) self.ui.analysisWidget.sigDbChanged.connect(self.analysisDbChanged) #self.logBtn = LogButton('Log') self.win.setStatusBar(StatusBar()) #self.win.statusBar().addPermanentWidget(self.logBtn) #self.win.statusBar().setFixedHeight(25) #self.win.statusBar().layout().setSpacing(0) #def hasInterface(self, interface): #return interface in ['DataSource'] def updateNewFolderList(self): self.ui.newFolderList.clear() conf = self.manager.config['folderTypes'] #print "folderTypes:", self.manager.config['folderTypes'].keys() self.ui.newFolderList.clear() self.ui.newFolderList.addItems(['New...', 'Folder'] + conf.keys()) def baseDirChanged(self): dh = self.manager.getBaseDir() self.baseDir = dh if dh is None: self.ui.baseDirText.setText('') else: self.ui.baseDirText.setText(dh.name()) self.ui.fileTreeWidget.setBaseDirHandle(dh) def loadLog(self, *args, **kwargs): pass def setLogDir(self): d = self.selectedFile() if not isinstance(d, DirHandle): d = d.parent() self.manager.setLogDir(d) def updateLogDir(self, d): self.ui.logDirText.setText(d.name(relativeTo=self.baseDir)) def setCurrentClicked(self): #print "click" handle = self.selectedFile() if handle is None: #print "no selection" return if not handle.isDir(): handle = handle.parent() #dh = self.manager.dirHandle(newDir) self.manager.setCurrentDir(handle) def currentDirChanged(self, name=None, change=None, args=()): if change in [None, 'moved', 'renamed', 'parent']: try: newDir = self.manager.getCurrentDir() except: newDir = None dirName = "" else: dirName = newDir.name(relativeTo=self.baseDir) self.ui.currentDirText.setText(str(dirName)) self.ui.fileTreeWidget.setCurrentDir(newDir) elif change == 'log': self.updateLogView(*args) if change == None: try: newDir = self.manager.getCurrentDir() except: newDir = None else: self.loadLog(newDir, self.ui.logView) #def loadLog(self, dirHandle, widget, recursive=0): #widget.clear() #log = dirHandle.readLog(recursive) #for line in self.logRender(log): #widget.append(line) def showFileDialog(self): bd = self.manager.getBaseDir() if bd is not None: self.dialog.setDirectory(bd.name()) self.dialog.show() def setBaseDir(self, dirName): if isinstance(dirName, list): if len(dirName) == 1: dirName = dirName[0] else: raise Exception("Caught. Please to be examined: %s" % str(dirName)) #if dirName is None: #dirName = QtGui.QFileDialog.getExistingDirectory() #if type(dirName) is QtCore.QStringList: # dirName = str(dirName[0]) #raise Exception("Caught. Please to be examined.") #if type(dirName) is QtCore.QStringList: #dirName = str(dirName[0]) #elif type(dirName) is QtCore.QString: #dirName = str(dirName) if dirName is None: return if os.path.isdir(dirName): self.manager.setBaseDir(dirName) else: raise Exception("Storage directory is invalid") def selectedFile(self): """Return the currently selected file""" items = self.ui.fileTreeWidget.selectedItems() if len(items) > 0: return items[0].handle else: return None #sel = list(self.ui.fileTreeWidget.selectedIndexes()) #if len(sel) == 0: # return None #if len(sel) == 1: # index = sel[0] #else: # raise Exception("Error - multiple items selected") ##print "index:", index.internalPointer() #if index.internalPointer() is None: # return None #return self.model.handle(index) def newFolder(self): if self.ui.newFolderList.currentIndex() < 1: return ftype = str(self.ui.newFolderList.currentText()) self.ui.newFolderList.setCurrentIndex(0) cdir = self.manager.getCurrentDir() if not cdir.isManaged(): cdir.createIndex() if ftype == 'Folder': nd = cdir.mkdir('NewFolder', autoIncrement=True) #item = self.model.handleIndex(nd) self.ui.fileTreeWidget.editItem(nd) else: spec = self.manager.config['folderTypes'][ftype] name = time.strftime(spec['name']) ## Determine where to put the new directory parent = cdir try: checkDir = cdir for i in range(5): if not checkDir.isManaged(): break inf = checkDir.info() if 'dirType' in inf and inf['dirType'] == ftype: parent = checkDir.parent() break #else: #print "dir no match:", spec, inf checkDir = checkDir.parent() except: printExc("Error while deciding where to put new folder (using currentDir by default)") ## make nd = parent.mkdir(name, autoIncrement=True) ## Add meta-info info = {'dirType': ftype} if spec.get('experimentalUnit', False): info['expUnit'] = True nd.setInfo(info) ## set display to info #self.showFileInfo(nd) #index = self.model.handleIndex(nd) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Clear) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Select) self.ui.fileTreeWidget.refresh(parent) ## fileTreeWidget waits a while before updating; force it to refresh immediately. self.ui.fileTreeWidget.select(nd) ##self.ui.fileInfo.setCurrentFile(nd) logMsg("Created new folder: %s" %nd.name(relativeTo=self.baseDir), msgType='status', importance=7) self.manager.setCurrentDir(nd) def fileSelectionChanged(self): #print "file selection changed" if self.selFile is not None: #QtCore.QObject.disconnect(self.selFile, QtCore.SIGNAL('changed'), self.selectedFileAltered) try: self.selFile.sigChanged.disconnect(self.selectedFileAltered) except TypeError: pass fh = self.selectedFile() self.manager.currentFile = fh ## Make this really easy to pick up from an interactive prompt. self.loadFile(fh) self.selFile = fh if fh is not None: #QtCore.QObject.connect(self.selFile, QtCore.SIGNAL('changed'), self.selectedFileAltered) self.selFile.sigChanged.connect(self.selectedFileAltered) def loadFile(self, fh): #self.ui.selectedLogView.clear() if fh is None: self.ui.fileInfo.setCurrentFile(None) self.ui.dataViewWidget.setCurrentFile(None) self.ui.logWidget.selectedFileChanged(None) self.ui.fileNameLabel.setText('') else: #self.ui.fileInfo.setCurrentFile(fh) #self.ui.dataViewWidget.setCurrentFile(fh) self.ui.fileNameLabel.setText(fh.name(relativeTo=self.baseDir)) #if fh.isDir(): #self.loadLog(fh, self.ui.selectedLogView, recursive=3) self.tabChanged() def tabChanged(self, n=None): if n is None: n = self.ui.fileDisplayTabs.currentIndex() fh = self.selectedFile() if n == 0: self.ui.fileInfo.setCurrentFile(fh) elif n == 1: self.ui.logWidget.selectedFileChanged(fh) #if fh.isDir(): #self.loadLog(fh, self.ui.selectedLogView, recursive=3) elif n == 2: self.ui.dataViewWidget.setCurrentFile(fh) def selectedFileAltered(self, name, change, args): if change in ['parent', 'renamed', 'moved'] and self.selFile is not None: #index = self.model.handleIndex(self.selFile) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Clear) #self.ui.fileTreeView.selectionModel().select(index, QtGui.QItemSelectionModel.Select) self.ui.fileTreeWidget.select(self.selFile) ## re-select file if it has moved. self.ui.fileNameLabel.setText(self.selFile.name(relativeTo=self.baseDir)) #self.fileSelectionChanged() #def logEntry(self): #text = str(self.ui.logEntryText.text()) #cd = self.manager.getCurrentDir() #self.ui.logEntryText.setText('') #if text == '' or cd is None: #return #cd.logMsg(text, {'source': 'user'}) #def updateLogView(self, *args): #msg = args[0] #self.ui.logView.append(self.logRender(msg)) ##print "new log msg" #def logRender(self, log): #returnList = True #if type(log) is dict: #log = [log] #returnList = False #elif type(log) is not list: #raise Exception('logRender requires dict or list of dicts as argument') #lines = [] #for msg in log: #t = time.strftime('%Y.%m.%d %H:%M:%S', time.localtime(msg['__timestamp__'])) #style = 'color: #000; font-style: normal' #sourceStyles = { #'user': '******' #} #if 'source' in msg and msg['source'] in sourceStyles: #style = sourceStyles[msg['source']] #parts = ["<span style='color: #888'>[%s]</span>" % t] #if 'subdir' in msg: #parts.append(msg['subdir']) #parts.append("<span style='%s'>%s</span>" % (style, msg['__message__'])) #lines.append(' '.join(parts)) #if returnList: #return lines #else: #return lines[0] def quit(self): ## Silly: needed to prevent lockup on some systems. #print " module quitting.." self.ui.fileTreeWidget.quit() self.ui.analysisWidget.quit() #sip.delete(self.dialog) #print " deleted dialog, calling superclass quit.." Module.quit(self) #print " module quit done" #print backtrace() def currentDatabase(self): return self.ui.analysisWidget.currentDatabase() def dataModel(self): return self.ui.analysisWidget.currentDataModel() def analysisDbChanged(self): self.sigAnalysisDbChanged.emit()
class LogWidget(Qt.QWidget): sigDisplayEntry = Qt.Signal(object) ## for thread-safetyness sigAddEntry = Qt.Signal(object) ## for thread-safetyness sigScrollToAnchor = Qt.Signal(object) # for internal use. def __init__(self, parent, manager): Qt.QWidget.__init__(self, parent) self.ui = LogWidgetTemplate.Ui_Form() self.manager = manager self.ui.setupUi(self) #self.ui.input.hide() self.ui.filterTree.topLevelItem(1).setExpanded(True) self.entries = [] ## stores all log entries in memory self.cache = { } ## for storing html strings of entries that have already been processed self.displayedEntries = [] #self.currentEntries = None ## recordArray that stores currently displayed entries -- so that if filters get more restrictive we can just refilter this list instead of filtering everything self.typeFilters = [] self.importanceFilter = 0 self.dirFilter = False self.entryArrayBuffer = np.zeros( 1000, dtype=[ ### a record array for quick filtering of entries ('index', 'int32'), ('importance', 'int32'), ('msgType', '|S10'), ('directory', '|S100'), ('entryId', 'int32') ]) self.entryArray = self.entryArrayBuffer[:0] self.filtersChanged() self.sigDisplayEntry.connect(self.displayEntry, Qt.Qt.QueuedConnection) self.sigAddEntry.connect(self.addEntry, Qt.Qt.QueuedConnection) self.ui.exportHtmlBtn.clicked.connect(self.exportHtml) self.ui.filterTree.itemChanged.connect(self.setCheckStates) self.ui.importanceSlider.valueChanged.connect(self.filtersChanged) #self.ui.logView.linkClicked.connect(self.linkClicked) self.ui.output.anchorClicked.connect(self.linkClicked) self.sigScrollToAnchor.connect(self.scrollToAnchor, Qt.Qt.QueuedConnection) #page = self.ui.logView.page() #page.setLinkDelegationPolicy(page.DelegateAllLinks) def loadFile(self, f): """Load the file, f. f must be able to be read by configfile.py""" log = configfile.readConfigFile(f) self.entries = [] self.entryArrayBuffer = np.zeros(len(log), dtype=[('index', 'int32'), ('importance', 'int32'), ('msgType', '|S10'), ('directory', '|S100'), ('entryId', 'int32')]) self.entryArray = self.entryArrayBuffer[:] i = 0 for k, v in log.items(): v['id'] = k[ 9:] ## record unique ID to facilitate HTML generation (javascript needs this ID) self.entries.append(v) self.entryArray[i] = np.array( [(i, v.get('importance', 5), v.get('msgType', 'status'), v.get('currentDir', ''), v.get('entryId', v['id']))], dtype=[('index', 'int32'), ('importance', 'int32'), ('msgType', '|S10'), ('directory', '|S100'), ('entryId', 'int32')]) i += 1 self.filterEntries( ) ## puts all entries through current filters and displays the ones that pass def addEntry(self, entry): ## All incoming messages begin here ## for thread-safetyness: isGuiThread = Qt.QThread.currentThread( ) == Qt.QCoreApplication.instance().thread() if not isGuiThread: self.sigAddEntry.emit(entry) return self.entries.append(entry) i = len(self.entryArray) entryDir = entry.get('currentDir', None) if entryDir is None: entryDir = '' arr = np.array([ (i, entry['importance'], entry['msgType'], entryDir, entry['id']) ], dtype=[('index', 'int32'), ('importance', 'int32'), ('msgType', '|S10'), ('directory', '|S100'), ('entryId', 'int32')]) ## make more room if needed if len(self.entryArrayBuffer) == len(self.entryArray): newArray = np.empty( len(self.entryArrayBuffer) + 1000, self.entryArrayBuffer.dtype) newArray[:len(self.entryArray)] = self.entryArray self.entryArrayBuffer = newArray self.entryArray = self.entryArrayBuffer[:len(self.entryArray) + 1] #self.entryArray[i] = [(i, entry['importance'], entry['msgType'], entry['currentDir'])] self.entryArray[i] = arr self.checkDisplay( entry) ## displays the entry if it passes the current filters #np.append(self.entryArray, np.array(i, [[i, entry['importance'], entry['msgType'], entry['currentDir']]]), dtype = [('index', int), ('importance', int), ('msgType', str), ('directory', str)]) def setCheckStates(self, item, column): if item == self.ui.filterTree.topLevelItem(1): if item.checkState(0): for i in range(item.childCount()): item.child(i).setCheckState(0, Qt.Qt.Checked) elif item.parent() == self.ui.filterTree.topLevelItem(1): if not item.checkState(0): self.ui.filterTree.topLevelItem(1).setCheckState( 0, Qt.Qt.Unchecked) self.filtersChanged() def filtersChanged(self): ### Update self.typeFilters, self.importanceFilter, and self.dirFilter to reflect changes. tree = self.ui.filterTree self.typeFilters = [] for i in range(tree.topLevelItem(1).childCount()): child = tree.topLevelItem(1).child(i) if tree.topLevelItem(1).checkState(0) or child.checkState(0): text = child.text(0) self.typeFilters.append(six.text_type(text)) self.importanceFilter = self.ui.importanceSlider.value() self.updateDirFilter() #self.dirFilter = self.manager.getDirOfSelectedFile().name() #else: #self.dirFilter = False self.filterEntries() def updateDirFilter(self, dh=None): if self.ui.filterTree.topLevelItem(0).checkState(0): if dh == None: self.dirFilter = self.manager.getDirOfSelectedFile().name() else: self.dirFilter = dh.name() else: self.dirFilter = False def filterEntries(self): """Runs each entry in self.entries through the filters and displays if it makes it through.""" ### make self.entries a record array, then filtering will be much faster (to OR true/false arrays, + them) typeMask = self.entryArray['msgType'] == '' for t in self.typeFilters: typeMask += self.entryArray['msgType'] == t mask = (self.entryArray['importance'] > self.importanceFilter) * typeMask if self.dirFilter != False: d = np.ascontiguousarray(self.entryArray['directory']) j = len(self.dirFilter) i = len(d) d = d.view(np.byte).reshape(i, 100)[:, :j] d = d.reshape(i * j).view('|S%d' % j) mask *= (d == self.dirFilter) self.ui.output.clear() global Stylesheet self.ui.output.document().setDefaultStyleSheet(Stylesheet) #global pageTemplate #self.ui.logView.setHtml(pageTemplate) indices = list(self.entryArray[mask]['index']) inds = indices #if self.dirFilter != False: #j = len(self.dirFilter) #for i, n in inds: #if not self.entries[n]['currentDir'][:j] == self.dirFilter: #indices.pop(i) self.displayEntry([self.entries[i] for i in indices]) def checkDisplay(self, entry): ### checks whether entry passes the current filters and displays it if it does. if entry['msgType'] not in self.typeFilters: return elif entry['importance'] < self.importanceFilter: return elif self.dirFilter is not False: if entry['currentDir'][:len(self.dirFilter)] != self.dirFilter: return else: self.displayEntry([entry]) def displayEntry(self, entries): ## entries should be a list of log entries ## for thread-safetyness: isGuiThread = Qt.QThread.currentThread( ) == Qt.QCoreApplication.instance().thread() if not isGuiThread: self.sigDisplayEntry.emit(entries) return for entry in entries: if id(entry) not in self.cache: self.cache[id(entry)] = self.generateEntryHtml(entry) ## determine message color: #if entry['msgType'] == 'status': #color = 'green' #elif entry['msgType'] == 'user': #color = 'blue' #elif entry['msgType'] == 'error': #color = 'red' #elif entry['msgType'] == 'warning': #color = '#DD4400' ## orange #else: #color = 'black' #if entry.has_key('exception') or entry.has_key('docs') or entry.has_key('reasons'): ##self.displayComplexMessage(entry, color) #self.displayComplexMessage(entry) #else: #self.displayText(entry['message'], entry, color, timeStamp=entry['timestamp']) #for x in self.cache[id(entry)]: #self.ui.output.appendHtml(x) html = self.cache[id(entry)] #frame = self.ui.logView.page().currentFrame() #isMax = frame.scrollBarValue(Qt.Qt.Vertical) == frame.scrollBarMaximum(Qt.Qt.Vertical) sb = self.ui.output.verticalScrollBar() isMax = sb.value() == sb.maximum() #frame.findFirstElement('body').appendInside(html) self.ui.output.append(html) self.displayedEntries.append(entry) if isMax: ## can't scroll to end until the web frame has processed the html change #frame.setScrollBarValue(Qt.Qt.Vertical, frame.scrollBarMaximum(Qt.Qt.Vertical)) ## Calling processEvents anywhere inside an error handler is forbidden ## because this can lead to Qt complaining about paint() recursion. #Qt.QApplication.processEvents() #self.ui.output.scrollToAnchor(str(entry['id'])) self.sigScrollToAnchor.emit(str( entry['id'])) ## queued connection #self.ui.logView.update() def scrollToAnchor(self, anchor): self.ui.output.scrollToAnchor(anchor) def generateEntryHtml(self, entry): msg = self.cleanText(entry['message']) reasons = "" docs = "" exc = "" if 'reasons' in entry: reasons = self.formatReasonStrForHTML(entry['reasons']) if 'docs' in entry: docs = self.formatDocsStrForHTML(entry['docs']) if entry.get('exception', None) is not None: exc = self.formatExceptionForHTML(entry, entryId=entry['id']) extra = reasons + docs + exc if extra != "": #extra = "<div class='logExtra'>" + extra + "</div>" extra = "<table class='logExtra'><tr><td>" + extra + "</td></tr></table>" #return """ #<div class='entry'> #<div class='%s'> #<span class='timestamp'>%s</span> #<span class='message'>%s</span> #%s #</div> #</div> #""" % (entry['msgType'], entry['timestamp'], msg, extra) return """ <a name="%s"/><table class='entry'><tr><td> <table class='%s'><tr><td> <span class='timestamp'>%s</span> <span class='message'>%s</span> %s </td></tr></table> </td></tr></table> """ % (str( entry['id']), entry['msgType'], entry['timestamp'], msg, extra) #if entry.has_key('exception') or entry.has_key('docs') or entry.has_key('reasons'): ##self.displayComplexMessage(entry, color) #return self.generateComplex(entry) #else: #return self.generateSimple(entry['message'], entry, color, timeStamp=entry['timestamp']) ##self.displayText(entry['message'], entry, color, timeStamp=entry['timestamp']) @staticmethod def cleanText(text): text = re.sub(r'&', '&', text) text = re.sub(r'>', '>', text) text = re.sub(r'<', '<', text) text = re.sub(r'\n', '<br/>\n', text) return text #def displayText(self, msg, entry, colorStr='black', timeStamp=None, clean=True): #if clean: #msg = self.cleanText(msg) #if msg[-1:] == '\n': #msg = msg[:-1] #msg = '<br />'.join(msg.split('\n')) #if timeStamp is not None: #strn = '<b style="color:black"> %s </b> <span style="color:%s"> %s </span>' % (timeStamp, colorStr, msg) #else: #strn = '<span style="color:%s"> %s </span>' % (colorStr, msg) ##self.ui.output.appendHtml(strn) #self.cache[id(entry)].append(strn) #def displayComplexMessage(self, entry, color='black'): #self.displayText(entry['message'], entry, color, timeStamp = entry['timestamp'], clean=True) #if entry.has_key('reasons'): #reasons = self.formatReasonStrForHTML(entry['reasons']) #self.displayText(reasons, entry, 'black', clean=False) #if entry.has_key('docs'): #docs = self.formatDocsStrForHTML(entry['docs']) #self.displayText(docs, entry, 'black', clean=False) #if entry.get('exception', None) is not None: #self.displayException(entry['exception'], entry, 'black', tracebacks=entry.get('traceback', None)) def formatExceptionForHTML(self, entry, exception=None, count=1, entryId=None): ### Here, exception is a dict that holds the message, reasons, docs, traceback and oldExceptions (which are also dicts, with the same entries) ## the count and tracebacks keywords are for calling recursively if exception is None: exception = entry['exception'] #if tracebacks is None: #tracebacks = [] indent = 10 text = self.cleanText(exception['message']) text = re.sub(r'^HelpfulException: ', '', text) #if exception.has_key('oldExc'): #self.displayText(" "*indent + str(count)+'. ' + text, entry, color, clean=False) #else: #self.displayText(" "*indent + str(count)+'. Original error: ' + text, entry, color, clean=False) messages = [text] #print "\n", messages, "\n" if 'reasons' in exception: reasons = self.formatReasonsStrForHTML(exception['reasons']) text += reasons #self.displayText(reasons, entry, color, clean=False) if 'docs' in exception: docs = self.formatDocsStrForHTML(exception['docs']) #self.displayText(docs, entry, color, clean=False) text += docs traceback = [ self.formatTracebackForHTML(exception['traceback'], count) ] text = [text] if 'oldExc' in exception: exc, tb, msgs = self.formatExceptionForHTML(entry, exception['oldExc'], count=count + 1) text.extend(exc) messages.extend(msgs) traceback.extend(tb) #else: #if len(tracebacks)==count+1: #n=0 #else: #n=1 #for i, tb in enumerate(tracebacks): #self.displayTraceback(tb, entry, number=i+n) if count == 1: exc = "<div class=\"exception\"><ol>" + "\n".join( ["<li>%s</li>" % ex for ex in text]) + "</ol></div>" tbStr = "\n".join([ "<li><b>%s</b><br/><span class='traceback'>%s</span></li>" % (messages[i], tb) for i, tb in enumerate(traceback) ]) #traceback = "<div class=\"traceback\" id=\"%s\"><ol>"%str(entryId) + tbStr + "</ol></div>" entry['tracebackHtml'] = tbStr #return exc + '<a href="#" onclick="showDiv(\'%s\')">Show traceback</a>'%str(entryId) + traceback return exc + '<a href="exc:%s">Show traceback %s</a>' % ( str(entryId), str(entryId)) else: return text, traceback, messages def formatTracebackForHTML(self, tb, number): try: tb = [ line for line in tb if not line.startswith("Traceback (most recent call last)") ] except: print("\n" + str(tb) + "\n") raise return re.sub(" ", " ", ("").join(map(self.cleanText, tb)))[:-1] #tb = [self.cleanText(strip(x)) for x in tb] #lines = [] #prefix = '' #for l in ''.join(tb).split('\n'): #if l == '': #continue #if l[:9] == "Traceback": #prefix = ' ' + str(number) + '. ' #continue #spaceCount = 0 #while l[spaceCount] == ' ': #spaceCount += 1 #if prefix is not '': #spaceCount -= 1 #lines.append(" "*(spaceCount*4) + prefix + l) #prefix = '' #return '<div class="traceback">' + '<br />'.join(lines) + '</div>' #self.displayText('<br />'.join(lines), entry, color, clean=False) def formatReasonsStrForHTML(self, reasons): #indent = 6 reasonStr = "<table class='reasons'><tr><td>Possible reasons include:\n<ul>\n" for r in reasons: r = self.cleanText(r) reasonStr += "<li>" + r + "</li>\n" #reasonStr += " "*22 + chr(97+i) + ". " + r + "<br>" reasonStr += "</ul></td></tr></table>\n" return reasonStr def formatDocsStrForHTML(self, docs): #indent = 6 docStr = "<div class='docRefs'>Relevant documentation:\n<ul>\n" for d in docs: d = self.cleanText(d) docStr += "<li><a href=\"doc:%s\">%s</a></li>\n" % (d, d) docStr += "</ul></div>\n" return docStr def exportHtml(self, fileName=False): #self.makeError1() if fileName is False: self.fileDialog = FileDialog(self, "Save HTML as...", self.manager.getCurrentDir().name()) #self.fileDialog.setFileMode(Qt.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(Qt.QFileDialog.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.exportHtml) return if fileName[-5:] != '.html': fileName += '.html' #doc = self.ui.output.document().toHtml('utf-8') #for e in self.displayedEntries: #if e.has_key('tracebackHtml'): #doc = re.sub(r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>'%(str(e['id']), str(e['id'])), e['tracebackHtml'], doc) global pageTemplate doc = pageTemplate for e in self.displayedEntries: doc += self.cache[id(e)] for e in self.displayedEntries: if 'tracebackHtml' in e: doc = re.sub( r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>' % (str(e['id']), str(e['id'])), e['tracebackHtml'], doc) #doc = self.ui.logView.page().currentFrame().toHtml() f = open(fileName, 'w') f.write(doc.encode('utf-8')) f.close() def makeError1(self): ### just for testing error logging try: self.makeError2() #print x except: t, exc, tb = sys.exc_info() #logExc(message="This button doesn't work", reasons='reason a, reason b', docs='documentation') #if isinstance(exc, HelpfulException): #exc.prependErr("Button doesn't work", (t,exc,tb), reasons = ["It's supposed to raise an error for testing purposes", "You're doing it wrong."]) #raise #else: printExc("This is the message sent to printExc.") #raise HelpfulException(message='This button does not work.', exc=(t, exc, tb), reasons=["It's supposed to raise an error for testing purposes", "You're doing it wrong."]) def makeError2(self): ### just for testing error logging try: print(y) except: t, exc, tb = sys.exc_info() raise HelpfulException(message='msg from makeError', exc=(t, exc, tb), reasons=["reason one", "reason 2"], docs=['what, you expect documentation?']) def linkClicked(self, url): url = url.toString() if url[:4] == 'doc:': self.manager.showDocumentation(url[4:]) elif url[:4] == 'exc:': cursor = self.ui.output.document().find('Show traceback %s' % url[4:]) try: tb = self.entries[int(url[4:]) - 1]['tracebackHtml'] except IndexError: try: tb = self.entries[self.entryArray[ self.entryArray['entryId'] == ( int(url[4:]))]['index']]['tracebackHtml'] except: print("requested index %d, but only %d entries exist." % (int(url[4:]) - 1, len(self.entries))) raise cursor.insertHtml(tb) def clear(self): #self.ui.logView.setHtml("") self.ui.output.clear() self.displayedEntryies = []