首页 > 解决方案 > 如何使 QTextBrowser 中的文本可选择?

问题描述

我希望能够以非自定义方式选择一段文本,就像您单击文本中间的网站链接一样(“你好,我的名字是 www.google.com”,www.google.com当你按下它时没有突出显示。当你按下它时,它会将你带到网站,就像我想要的文本一样。我想让它像“你好,我的名字是 Jeff,我住在伦敦,我吃每天土豆”我希望用户能够单独选择每个句子(“你好,我的名字是杰夫”)单独,(“我住在伦敦”)单独,所以当用户将鼠标光标移到一个句子突出显示(比如准备被选中),然后我想给它添加一些功能。

这是一个类似的项目,检查上面的文本,而不是下面的文本以及它对鼠标的反应。

http://quran.ksu.edu.sa/tafseer/tabary/sura10-aya18.html

标签: pythonpyqtpyqt5qtextbrowser

解决方案


这是一个开始。在下面的代码中,文本中的可点击短语被翻译成文本光标的有序列表。当鼠标移动时(即当一个mouseMove事件发生时),鼠标指针下的文本位置与这个文本光标列表进行比较。如果该位置落在列表中光标的范围内,则通过设置浏览器的额外选择来突出显示相应的短语。当单击鼠标按钮并且鼠标悬停在可单击的短语上时,会发出一个信号,其中包含与文本中所选短语相对应的文本光标。然后可以将此信号连接到另一个小部件的插槽以进行进一步的操作(例如打开消息框,如下例所示)。

from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QTextBrowser, QApplication, QMessageBox
from PyQt5.QtGui import QFont, QSyntaxHighlighter, QTextCharFormat, QTextCursor

import bisect, re


class MyHighlighter(QSyntaxHighlighter):
    def __init__(self, keywords, parent):
        super().__init__(parent)
        self.keywords = keywords

    def highlightBlock(self, block):
        if not self.keywords:
            return
        charFormat = QTextCharFormat()
        charFormat.setFontWeight(QFont.Bold)
        charFormat.setForeground(Qt.darkMagenta)
        regex = re.compile('|'.join(self.keywords), re.IGNORECASE)
        result = regex.search(block, 0)
        while result:
            self.setFormat(result.start(),result.end()-result.start(), charFormat)
            result = regex.search(block, result.end())


class MyBrowser(QTextBrowser):
    text_clicked = pyqtSignal("QTextCursor")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setMouseTracking(True)
        self.setTextInteractionFlags(Qt.NoTextInteraction)

        # self.phrases contains all phrases that should be clickable.
        self.phrases = set()
        self.cursors = []

        # ExtraSelection object for highlighting phrases under the mouse cursor
        self.selection = QTextBrowser.ExtraSelection()
        self.selection.format.setBackground(Qt.blue)
        self.selection.format.setForeground(Qt.white)
        self.selected_cursor = None

        # custom highlighter for highlighting all phrases
        self.highlighter = MyHighlighter(self.phrases, self)
        self.document().contentsChange.connect(self.text_has_changed)

    @property
    def selected_cursor(self):
        return None if self.selection.cursor == QTextCursor() else self.selection.cursor

    @selected_cursor.setter
    def selected_cursor(self, cursor):
        if cursor is None:
            cursor = QTextCursor()
        if self.selection.cursor != cursor:
            self.selection.cursor = cursor
            self.setExtraSelections([self.selection])

    def mouseMoveEvent(self, event):
        ''' Update currently selected cursor '''
        cursor = self.cursorForPosition(event.pos())
        self.selected_cursor = self.find_selected_cursor(cursor)

    def mouseReleaseEvent(self, event):
        ''' Emit self.selected_cursor signal when currently hovering over selectable phrase'''
        if self.selected_cursor:
            self.text_clicked.emit(self.selected_cursor)
            self.selected_cursor = None

    def add_phrase(self, phrase):
        ''' Add phrase to set of phrases and update list of text cursors'''
        if phrase not in self.phrases:
            self.phrases.add(phrase)
            self.find_cursors(phrase)
            self.highlighter.rehighlight()

    def find_cursors(self, phrase):
        ''' Find all occurrences of phrase in the current document and add corresponding text cursor
        to self.cursors '''
        if not phrase:
            return
        self.moveCursor(self.textCursor().Start)
        while self.find(phrase):
            cursor = self.textCursor()
            bisect.insort(self.cursors, cursor)
        self.moveCursor(self.textCursor().Start)

    def find_selected_cursor(self, cursor):
        ''' return text cursor corresponding to current mouse position or None if mouse not currently
        over selectable phrase'''
        position = cursor.position()
        index = bisect.bisect(self.cursors, cursor)
        if index < len(self.cursors) and self.cursors[index].anchor() <= position:
            return self.cursors[index]
        return None

    def text_has_changed(self):
        self.cursors.clear()
        self.selected_cursor = None
        for phrase in self.phrases:
            self.find_cursors(phrase)
            self.highlighter.rehighlight()


def text_message(widget):
    def inner(cursor):
        text = cursor.selectedText()
        pos = cursor.selectionStart()
        QMessageBox.information(widget, 'Information',
                f'You have clicked on the phrase <b>{text}</b><br>'
                f'which starts at position {pos} in the text')
    return inner


if __name__=="__main__":
    app = QApplication([])
    window = MyBrowser()
    window.resize(400,300)
    information = text_message(window)

    text = '''
    <h1>Title</h1>
    <p>This is a random text with. The following words are highlighted</p>
    <ul>
    <li>keyword1</li>
    <li>keyword2</li>
    </ul>
    <p>Click on either keyword1 or keyword2 to get more info. 
    '''

    window.add_phrase('keyword1')
    window.add_phrase('keyword2')
    window.setText(text)
    window.text_clicked.connect(information)
    window.show()
    app.exec()

推荐阅读