在QPlainTextEdit中清除对突出显示光标的选择

问题描述

example

我构建了一个自定义的PlainTextEdit作为logviewer小部件。我有一个外部lineEdit小部件来指定搜索模式(类似于您使用ctrl + f在网站上查找文本的方式)。 lineEdits textChanged信号连接到_find_text()方法。 textEdit应该突出显示各个匹配项。但是,_clear_search_data()方法似乎存在一些问题,因为在以前的匹配过程中使用self._highlight_cursor进行的选择仍然会突出显示

class LogTextEdit(QtWidgets.QPlainTextEdit):
    """
    """

    mousepressedSignal = QtCore.Signal(object)
    matchCounterChanged = QtCore.Signal(tuple)
    def __init__(self,parent):
        """
        """
        super(LogTextEdit,self).__init__(parent)

        # some settings
        self.setReadOnly(True)
        self.setMaximumBlockCount(20000)

        self.master_document = QtGui.QTextDocument() # always log against master
        self.master_doclay = QtWidgets.QPlainTextDocumentLayout(self.master_document)
        self.master_document.setDocumentLayout(self.master_doclay)
        self.proxy_document = QtGui.QTextDocument() # display the filtered document
        self.proxy_doclay = QtWidgets.QPlainTextDocumentLayout(self.proxy_document)
        self.proxy_document.setDocumentLayout(self.proxy_doclay)
        self.setDocument(self.proxy_document)

        # members
        self._matches = []
        self._current_match = 0
        self._current_search = ("",False) 
        self._content_timestamp = 0
        self._search_timestamp = 0
        self._first_visible_index = 0
        self._last_visible_index = 0

        self._matches_to_highlight = set()
        self._matches_label = QtWidgets.QLabel()

        self._cursor = self.textCursor()
        pos = QtCore.QPoint(0,0)
        self._highlight_cursor = self.cursorForPosition(pos)

        # text formatting related 
        self._format = QtGui.QTextCharFormat()
        self._format.setBackground(QtCore.Qt.red)
        self.font = self.document().defaultFont()
        self.font.setFamily('Courier New') # fixed width font
        self.document().setDefaultFont(self.font)
        self.reset_text_format = QtGui.QTextCharFormat()
        self.reset_text_format.setFont(self.document().defaultFont())

        # right click context menu
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.copy_action = QtWidgets.QAction('copy',self)
        self.copy_action.setStatusTip('copy Selection')
        self.copy_action.setShortcut('Ctrl+C')
        self.addAction(self.copy_action)

        # Initialize state
        self.updateLineNumberAreaWidth()
        self.highlightCurrentLine()

        # Signals
        # linearea
        self.blockCountChanged.connect(self.updateLineNumberAreaWidth)
        self.updateRequest.connect(self.updateLineNumberArea)
        self.cursorPositionChanged.connect(self.highlightCurrentLine)

        # copy
        self.customContextMenuRequested.connect(self.context_menu)
        self.copy_action.triggered.connect(lambda: self.copy_selection(QtGui.QClipboard.Mode.Clipboard))

    def appendMessage(self,msg:str,document: QtGui.QTextDocument):
        cursor = QtGui.QTextCursor(document)
        cursor.movePosition(QtGui.QTextCursor.MoveOperation.End,QtGui.QTextCursor.MoveMode.MoveAnchor)
        cursor.beginEditBlock()
        cursor.insertBlock()
        cursor.insertText(msg)
        cursor.endEditBlock()
        
        self._content_timestamp = time.time()

    def _move_to_next_match(self):
        """
        Moves the cursor to the next occurrence of search pattern match,scrolling up/down through the content to display the cursor position.
        When the cursor is not set and this method is called,the cursor will
        be moved to the first match. Subsequent calls move the cursor through
        the next matches (moving forward). When the cursor is at the last match
        and this method is called,the cursor will be moved back to the first
        match. If there are no matches,this method does nothing.
        """

        if not self._matches:
            return

        if self._current_match >= len(self._matches):
            self._current_match = 0

        self._move_to_match(self._matches[self._current_match][0],self._matches[self._current_match][1])
        self._current_match += 1

    def _move_to_prev_match(self):
        """
        Moves the cursor to the prevIoUs occurrence of search pattern match,scrolling up/down through the content to display the cursor position.
        When called the first time,it moves the cursor to the last match,subsequent calls move the cursor backwards through the matches. When
        the cursor is at the first match and this method is called,the cursor
        will be moved back to the last match
        If there are no matches,this method does nothing.
        """

        if not self._matches:
            return

        if self._current_match < 0:
            self._current_match = len(self._matches) - 1

        self._move_to_match(self._matches[self._current_match][0],self._matches[self._current_match][1])
        self._current_match -= 1

    def _move_to_match(self,pos,length):
        """
        Moves the cursor in the content Box to the given pos,then moves it
        forwards by "length" steps,selecting the characters in between
        @param pos: The starting position to move the cursor to
        @type pos: int
        @param length: The number of steps to move+select after the starting
                       index
        @type length: int
        @postcondition: The cursor is moved to pos,the characters between pos
                        and length are selected,and the content is scrolled
                        up/down to ensure the cursor is visible
        """

        self._cursor.setPosition(pos)
        self._cursor.movePosition(QtGui.QTextCursor.Right,QtGui.QTextCursor.KeepAnchor,length)
        self.setTextCursor(self._cursor)
        self.ensureCursorVisible()
        #self._scrollbar_value = self._log_scrollbar.value()
        self._highlight_matches()
        self._matches_label.setText('%d:%d matches'
                                    % (self._current_match + 1,len(self._matches)))

    def _find_text(self,pattern:str,flags:int,isRegexPattern:bool):
        """
        Finds and stores the list of text fragments matching the search pattern
        entered in the search Box.
        @postcondition: The text matching the search pattern is stored for
                        later access & processing
        """

        prev_search = self._current_search
        self._current_search = (pattern,flags,isRegexPattern)
        search_has_changed = (
            self._current_search[0] != prev_search[0] or 
            self._current_search[1] != prev_search[1] or 
            self._current_search[2] != prev_search[2]
        )

        if not self._current_search[0]:  # nothing to search for,clear search data
            self._clear_search_data()
            return

        if self._content_timestamp <= self._search_timestamp and not search_has_changed:
            self._move_to_next_match()
            return

        # New Search
        self._clear_search_data()
        try:
            match_objects = re.finditer(str(pattern),self.toPlainText(),flags)
            for match in match_objects:
                index = match.start()
                length = len(match.group(0))
                self._matches.append((index,length))
            if not self._matches:
                self._matches_label.setStyleSheet('QLabel {color : gray}')
                self._matches_label.setText('No Matches Found')
            self._matches_to_highlight = set(self._matches)
            self._update_visible_indices()
            self._highlight_matches()
            self._search_timestamp = time.time()

            # Start navigating
            self._current_match = 0
            self._move_to_next_match()
        except re.error as err:
            self._matches_label.setText('ERROR: %s' % str(err))
            self._matches_label.setStyleSheet('QLabel {color : indianred}')

    def _highlight_matches(self):
        """
        Highlights the matches closest to the current match
        (current = the one the cursor is at)
        (closest = up to 300 matches before + up to 300 matches after)
        @postcondition: The matches closest to the current match have a new
                        background color (Red)
        """

        if not self._matches_to_highlight or not self._matches:
            return  # nothing to match

        # Update matches around the current one (300 before and 300 after)
        highlight = self._matches[max(self._current_match - 300,0):
                                  min(self._current_match + 300,len(self._matches))]

        matches = list(set(highlight).intersection(self._matches_to_highlight))
        for match in matches:
            self._highlight_cursor.setPosition(match[0])
            self._highlight_cursor.movePosition(QtGui.QTextCursor.Right,match[-1])
            self._highlight_cursor.setCharFormat(self._format)
            self._matches_to_highlight.discard(match)

    def _clear_search_data(self):
        """
        Removes the text in the search pattern Box,clears all highlights and
        stored search data
        @postcondition: The text in the search field is removed,match list is
                        cleared,and format/selection in the main content Box
                        are also removed.
        """

        self._matches = []
        self._matches_to_highlight = set()
        self._search_timestamp = 0
        self._matches_label.setText('')

        format = QtGui.QTextCharFormat()
        self._highlight_cursor.setPosition(QtGui.QTextCursor.Start)
        self._highlight_cursor.movePosition(QtGui.QTextCursor.End,mode=QtGui.QTextCursor.KeepAnchor)
        self._highlight_cursor.setCharFormat(format)
        self._highlight_cursor.clearSelection()

    def _update_visible_indices(self):
        """
        Updates the stored first & last visible text content indices so we
        can focus operations like highlighting on text that is visible
        @postcondition: The _first_visible_index & _last_visible_index are
                        up to date (in sync with the current viewport)
        """

        viewport = self.viewport()
        try:
            top_left = QtCore.QPoint(0,0)
            bottom_right = QtCore.QPoint(viewport.width() - 1,viewport.height() - 1)
            first = self.cursorForPosition(top_left).position()
            last = self.cursorForPosition(bottom_right).position()
            self._first_visible_index = first
            self._last_visible_index = last
        except IndexError:  # When there's nothing in the content Box
            pass

编辑: 使用QSyntaxHightlighter是突出显示的一种优雅方法,但是出于两个原因,我必须使用manuel方法 a)日志文件可能会变得很沉重,因此上述解决方案仅允许我们限制可见行范围内的突出显示 b)我必须能够在文档中的匹配项之间跳转

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...