获取 QTextEdit 选择的边界框

Getting the bounding box of QTextEdit selection

我正在尝试获取存储在列表中的一系列文本选择的边界框。边界框是可以包含整个选区的最小矩形。列表中的每个项目都有一个起点和终点,以 QTextEdit window 开头的字符为单位,还有一个字母标识符。 QTextEdit.cursorRect(cursor) 应该这样做,但会产生无意义的框尺寸:

id: A -- PySide.QtCore.QRect(0, 0, 1, 10)
id: B -- PySide.QtCore.QRect(0, 0, 1, 10)
id: C -- PySide.QtCore.QRect(0, 0, 1, 10)

所有选择都从不同的点开始,因此 (0,0) 在视点坐标中不正确。此外,其中一些跨越多行,因此宽度和高度应该有所不同。问题可能是光标处于循环中,直到循环完成后我才用 setTextCursor 设置它。我这样做是因为我还将选区渲染为高光。如何让 cursorRect 正常工作或以其他方式为每个选择获得一个单独的边界框?这是代码:

import sys
from PySide.QtCore import *
from PySide.QtGui import *

db = ((5,8,'A'),(20,35,'B'),(45,60,'C')) # start, end, and identifier of highlights

class TextEditor(QTextEdit):

    def __init__(self, parent=None):
        super().__init__(parent)
        text="This is example text that is several lines\nlong and also\nstrangely broken up and can be\nwrapped."
        self.setText(text)
        for n in range(0,len(db)):
            row = db[n]
            startChar = row[0]
            endChar = row[1]
            id = row[2]

            cursor = self.textCursor()
            cursor.setPosition(startChar)
            cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, endChar-startChar)

            rect = self.cursorRect(cursor)
            print("id: %s -- %s" % (id,str(rect)))

            charfmt = cursor.charFormat()
            charfmt.setBackground(QColor(Qt.yellow))
            cursor.setCharFormat(charfmt)
        cursor.clearSelection()
        self.setTextCursor(cursor)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    editor = TextEditor()
    editor.show()
    app.exec_()
    sys.exit(app.exec_())

编辑 1:

这是程序中的文本。我将为突出显示的文本使用 CAPS:

This IS example text THAT IS SEVERAL lines
loNG AND ALSO
STRangely broken up and can be
wrapped.

让我们假设每个字符都是 10 x 10 像素。 "IS " 从 5 个字符开始并扩展 3 个字符(包括末尾的 space)。因此,"I" 的左上角将位于 x=50,y=0。 space 的右下角位于 x=80,y=10。如果边界矩形以坐标给出,它将是 (50,0,80,10)。如果边界矩形以起始坐标和大小给出,它将是 (50,0,30,10).

第二行是高亮,一直延续到第三行。它最左边的字符是第 3 行开头的 "S",即 x=0。它最右边的字符是 "ALSO" 中的 "O",它以 x=130 结尾。它的最上面一行是第二行,从 y=10 开始。它最底下的一行是第三行,结束于 y=30。因此,边界框的坐标为 (0,10,130,30) 或起点和大小为 (0,10,130,20)。

下面是为数据库中的信息指定的所有突出显示部分查找边界框的初步尝试。为了明确每个边界框涵盖的内容,示例脚本显示了相应的橡皮筋。结果如下:

调整 window 的大小将根据当前的自动换行自动重新计算框。请注意,如果 window 变得非常小,这可能意味着几个框相互重叠。

我已将此作为单独的方法实现,因为更改 char 格式可能会重新设置文档布局。因此,在第二遍中计算框更可靠。当然,这也允许在 window 调整大小时进行动态重新计算。

import sys
from PySide.QtCore import *
from PySide.QtGui import *

db = ((5,8,'A'),(20,35,'B'),(45,60,'C')) # start, end, and identifier of highlights

class TextEditor(QTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent)
        text="This is example text that is several lines\nlong and also\nstrangely broken up and can be\nwrapped."
        self.setText(text)
        cursor = self.textCursor()
        for n in range(0,len(db)):
            row = db[n]
            startChar = row[0]
            endChar = row[1]
            id = row[2]
            cursor.setPosition(startChar)
            cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, endChar-startChar)
            charfmt = cursor.charFormat()
            charfmt.setBackground(QColor(Qt.yellow))
            cursor.setCharFormat(charfmt)
        cursor.clearSelection()
        self.setTextCursor(cursor)

    def getBoundingRect(self, start, end):
        cursor = self.textCursor()
        cursor.setPosition(end)
        last_rect = end_rect = self.cursorRect(cursor)
        cursor.setPosition(start)
        first_rect = start_rect = self.cursorRect(cursor)
        if start_rect.y() != end_rect.y():
            cursor.movePosition(QTextCursor.StartOfLine)
            first_rect = last_rect = self.cursorRect(cursor)
            while True:
                cursor.movePosition(QTextCursor.EndOfLine)
                rect = self.cursorRect(cursor)
                if rect.y() < end_rect.y() and rect.x() > last_rect.x():
                    last_rect = rect
                moved = cursor.movePosition(QTextCursor.NextCharacter)
                if not moved or rect.y() > end_rect.y():
                    break
            last_rect = last_rect.united(end_rect)
        return first_rect.united(last_rect)

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.edit = TextEditor(self)
        layout = QVBoxLayout(self)
        layout.addWidget(self.edit)
        self.boxes = []

    def showBoxes(self):
        while self.boxes:
            self.boxes.pop().deleteLater()
        viewport = self.edit.viewport()
        for start, end, ident in db:
            rect = self.edit.getBoundingRect(start, end)
            box = QRubberBand(QRubberBand.Rectangle, viewport)
            box.setGeometry(rect)
            box.show()
            self.boxes.append(box)

    def resizeEvent(self, event):
        self.showBoxes()
        super().resizeEvent(event)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = Window()
    window.setGeometry(800, 100, 350, 150)
    window.show()
    window.showBoxes()
    sys.exit(app.exec_())