예제 #1
0
파일: LogWindow.py 프로젝트: hiuwo/acq4
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"&", "&amp;", text)
        text = re.sub(r">", "&gt;", text)
        text = re.sub(r"<", "&lt;", 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("&nbsp;"*indent + str(count)+'. ' + text, entry, color, clean=False)
        # else:
        # self.displayText("&nbsp;"*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(" ", "&nbsp;", ("").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("&nbsp;"*(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 += "&nbsp;"*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 = []
예제 #2
0
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)
예제 #3
0
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'&', '&amp;', text)
        text = re.sub(r'>', '&gt;', text)
        text = re.sub(r'<', '&lt;', 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("&nbsp;"*indent + str(count)+'. ' + text, entry, color, clean=False)
        #else:
        #self.displayText("&nbsp;"*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(" ", "&nbsp;", ("").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("&nbsp;"*(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 += "&nbsp;"*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 = []