代码之家  ›  专栏  ›  技术社区  ›  panofish

在滚动条中突出显示错误的日志查看器

  •  0
  • panofish  · 技术社区  · 1 年前

    我正在使用PySide6在Python 3.11中开发一个自定义记事本风格的日志查看器。

    ui是用QtDesigner创建的,必须用pyuic命令编译成python,并用import“main_ui”导入到以下代码中。

    应用程序将名为sample.log的文件加载到QPlainTextEdit字段中。 然后,用户可以单击按钮查找并突出显示包含“Error”子字符串的所有行。

    每个突出显示的行的相对位置在自定义滚动条中用红线表示。

    我的问题是如何正确计算滚动条中红线的位置? 我目前的逻辑是可行的,但立场并不准确。

    这就是界面的样子:

    enter image description here

    本文提供了所有必要的代码,但也可以通过此zip文件下载 logtool.zip 其中包括这些文件:

    1. logtool.py
    2. main.ui
    3. main_ui.py(使用Python6/Scripts/pyside6-uic.exe从main.ui构建)
    4. sample.log

    这是主程序logtool.py的样子:

    import sys
    import os
    import os.path
    
    from PySide6 import QtWidgets
    from PySide6.QtCore import Qt, Slot
    from PySide6.QtWidgets import QMainWindow, QScrollBar 
    from PySide6.QtGui import QTextCursor, QTextCharFormat, QColor, QPainter, QPen 
    
    import main_ui
    
    substring_list = ["error", "exception", "fail"]     # Strings to look for in log file that indicate an error
    
    class MyScroll(QScrollBar):
    
        def __init__(self, parent=None):
            super(MyScroll, self).__init__(parent)
    
        def paintEvent(self, event):
            super().paintEvent(event)  
    
            painter = QPainter(self)
            pen = QPen()
            pen.setWidth(3)
            pen.setColor(Qt.red)
            pen.setStyle(Qt.SolidLine)
            painter.setPen(pen)
    
            try: # return if values is not set
                self.values  
            except:
                return
    
            for value in self.values:
                painter.drawLine(0, value, 15, value)     
    
        def draw(self, values):
            self.values = values
            self.update()
    
    class MainWindow(QMainWindow, main_ui.Ui_main):
    
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            self.setupUi(self)   # setup the GUI --> function generated by pyuic4
    
            self.txtEdit.setCenterOnScroll(True)
    
            fullpath = "sample.log" # aos_diag log file
            self.readfile(fullpath)
    
            self.lines = []
    
            self.scroll = MyScroll()
            self.txtEdit.setVerticalScrollBar(self.scroll)
    
            self.show()
    
        def calc_drawline_pixel(self, lineno):
    
            widget = self.txtEdit.verticalScrollBar()
    
            total_lines = widget.maximum()
            scroll_pixel_height = widget.height()
    
            factor = lineno / total_lines
            uparrow_height = 15
            draw_at_pixel = int(factor * scroll_pixel_height) + uparrow_height
    
            return draw_at_pixel
    
        @Slot()
        def on_btnMarkErrors_clicked(self):
    
            self.lines = []  # lines is a list of line numbers which match search
            self.pos = 0
    
            string = self.txtEdit.toPlainText()
    
            reclist = string.split("\n")
    
            for lineno, line in enumerate(reclist):
                flag = any(substring.lower() in line.lower() for substring in substring_list)  # if any substring in list is in the line then return true
                if flag:
                    self.markline(lineno)
                    self.lines.append(lineno)
    
            self.lines = sorted(self.lines) 
    
            # fill values list and pass to draw method of scrollbar
            values = []
            for lineno in self.lines:
                pixel = self.calc_drawline_pixel(lineno)
                values.append(pixel)
    
            self.scroll.draw(values)
    
        def markline(self, line_number):
            """marks line with red highlighter"""
    
            widget = self.txtEdit
            cursor = QTextCursor(widget.document().findBlockByLineNumber(line_number))  # position cursor on the given line
            cursor.select(QTextCursor.LineUnderCursor)  # Select the line.
            fmt = QTextCharFormat()
            RED = QColor(228,191,190)
            fmt.setBackground(RED)
            cursor.mergeCharFormat(fmt) # Apply the format to the selected text
    
        def readfile(self, fullpath):
    
            fullpath = fullpath.replace("\\", "/")   # flip all backslashes to forward slashes because python prefers
            path,file = os.path.split(fullpath)     # extract path and file from fullpath
            fin = open(fullpath, encoding="utf8", errors='ignore')
            content = fin.read()
            fin.close()
                
            self.txtEdit.setPlainText(content)        
    
    if __name__ == "__main__":
    
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle("Fusion")
        myapp = MainWindow()      
        rc = app.exec()
        sys.exit(rc)   
    

    这就是main_ui.py的样子:

    from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
        QMetaObject, QObject, QPoint, QRect,
        QSize, QTime, QUrl, Qt)
    from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
        QFont, QFontDatabase, QGradient, QIcon,
        QImage, QKeySequence, QLinearGradient, QPainter,
        QPalette, QPixmap, QRadialGradient, QTransform)
    from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QMainWindow,
        QPlainTextEdit, QPushButton, QSizePolicy, QSpacerItem,
        QStatusBar, QVBoxLayout, QWidget)
    
    class Ui_main(object):
        def setupUi(self, main):
            if not main.objectName():
                main.setObjectName(u"main")
            main.resize(602, 463)
            self.centralwidget = QWidget(main)
            self.centralwidget.setObjectName(u"centralwidget")
            self.verticalLayout = QVBoxLayout(self.centralwidget)
            self.verticalLayout.setObjectName(u"verticalLayout")
            self.frame = QFrame(self.centralwidget)
            self.frame.setObjectName(u"frame")
            self.frame.setFrameShape(QFrame.NoFrame)
            self.frame.setFrameShadow(QFrame.Raised)
            self.horizontalLayout = QHBoxLayout(self.frame)
            self.horizontalLayout.setObjectName(u"horizontalLayout")
            self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
            self.btnMarkErrors = QPushButton(self.frame)
            self.btnMarkErrors.setObjectName(u"btnMarkErrors")
    
            self.horizontalLayout.addWidget(self.btnMarkErrors)
    
            self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
    
            self.horizontalLayout.addItem(self.horizontalSpacer_2)
    
    
            self.verticalLayout.addWidget(self.frame)
    
            self.txtEdit = QPlainTextEdit(self.centralwidget)
            self.txtEdit.setObjectName(u"txtEdit")
            font = QFont()
            font.setFamilies([u"Consolas"])
            self.txtEdit.setFont(font)
            self.txtEdit.setLineWrapMode(QPlainTextEdit.NoWrap)
    
            self.verticalLayout.addWidget(self.txtEdit)
    
            main.setCentralWidget(self.centralwidget)
            self.statusbar = QStatusBar(main)
            self.statusbar.setObjectName(u"statusbar")
            main.setStatusBar(self.statusbar)
    
            self.retranslateUi(main)
    
            QMetaObject.connectSlotsByName(main)
        # setupUi
    
        def retranslateUi(self, main):
            main.setWindowTitle(QCoreApplication.translate("main", u"Log Viewer", None))
            self.btnMarkErrors.setText(QCoreApplication.translate("main", u"Mark Errors", None))
    
    0 回复  |  直到 1 年前
        1
  •  1
  •   alec    1 年前

    一种选择是设置 sliderPosition 将QStyleOptionSlider设置为文本行在视口中居中的行号,然后使用它来获取滚动条滑块的中心位置 QStyle.subControlRect() .

    def calc_drawline_pixel(self, lineno):
        value = lineno - self.scroll.pageStep() // 2
        opt = QtWidgets.QStyleOptionSlider()
        self.scroll.initStyleOption(opt)
        opt.sliderPosition = value
        draw_at_pixel = self.scroll.style().subControlRect(
            QtWidgets.QStyle.CC_ScrollBar, opt, QtWidgets.QStyle.SC_ScrollBarSlider
            ).center().y()
        
        if value < 0: # lineno is less than half the pageStep, sliderPosition was capped at the minimum (0)
            opt.sliderPosition = -value
            dy = self.scroll.style().subControlRect(
                QtWidgets.QStyle.CC_ScrollBar, opt, QtWidgets.QStyle.SC_ScrollBarSlider
                ).center().y() - draw_at_pixel
            draw_at_pixel -= dy
            
        return draw_at_pixel
    
    推荐文章