user51515151
user51515151

Reputation: 48

PySide6. QTextBlock.QTextLayout.setFormats

How to work with setFormats?

The program displays QTextEdit in QMainWindow.
The task is to find the word "import" and highlight it in red using block.layout.setFormats (I don't want the undo-redo history to include the appearance change, and I don't want to use QSyntaxHighlighter).

I don't understand why when finding the word "import" and then setFormats, the corresponding block becomes invisible.

from PySide6 import QtWidgets, QtCore, QtGui


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QtWidgets.QWidget(self)
        self.central_widget.setLayout(QtWidgets.QVBoxLayout(self.central_widget))
        self.text_editor = QtWidgets.QTextEdit(self.central_widget)
        self.central_widget.layout().addWidget(self.text_editor)
        self.setCentralWidget(self.central_widget)
        self.text_editor.setFont(QtGui.QFont('Arial', 14))
        self.text_editor.textChanged.connect(self.text_changed)

    @QtCore.Slot()
    def text_changed(self):
        word = 'import'
        text_cursor_before_find_op = self.text_editor.textCursor()

        self.text_editor.moveCursor(QtGui.QTextCursor.MoveOperation.Start)
        found = self.text_editor.find(word)
        if found:
            text_cursor = self.text_editor.textCursor()
            text_cursor.setPosition(text_cursor.position())
            block = text_cursor.block()
            position_in_block = text_cursor.positionInBlock() - len(word)
            format_range = QtGui.QTextLayout.FormatRange()
            format_range.start = position_in_block
            format_range.length = len(word)
            format_range.format = self.text_editor.currentCharFormat()
            format_range.format.setForeground(QtGui.QColor('#FF0000'))
            formats = [format_range]
            block.layout().setFormats(formats)
            print(position_in_block,
                  repr(block.text()),
                  (format_range.start, format_range.length, format_range.format),
                  block.isValid(), block.isVisible(),
                  sep='\n', end='\n\n')

        self.text_editor.setTextCursor(text_cursor_before_find_op)


app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec()

Upvotes: 0

Views: 270

Answers (2)

Haru
Haru

Reputation: 2083

I think this is a kind of bug.

In other version, in the era of setAdditionalFormats, I could have colorized the text without problem.

I executed your code, and typed "import". It seemed that the draw function on the block(QTextLayout.draw function) isn't be called, since the cursor came to stop blinking.

QTextLayout.FormatRange is usually used in a draw function of QAbstractTextDocumentLayout. And cursor(QTextLayout.drawCursor function) is drawn in the same function.

QTextLayout has draw function and this is also used in that function.

class TextDocumentLayout(...


   def draw(self, ...):
       
       layout.draw(..., , formatRanges)
       layout.drawCursor(...)

This drawing seems that it doesn't work well by some reasons. (I assume that setFormats is related to something flags).

At any rate, I found out that we can avoid this bug by expanding the size of textedit horizontally by mouse handle.

But it is not good we use mouse handle one by one, I use resizeEvent of QTextEdit. this doesn't work well if we expand the size vertically. I don't know why...

resizeEvent have a side effect of invoking repainting. But if I use repaint() function directly, repaint is not happend... I don't know why... Anyway, I think this problem is a kind of Qt bug.

from PySide6 import QtWidgets, QtCore, QtGui


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QtWidgets.QWidget(self)
        self.central_widget.setLayout(QtWidgets.QVBoxLayout(self.central_widget))
        self.text_editor = QtWidgets.QTextEdit(self.central_widget)
        self.central_widget.layout().addWidget(self.text_editor)
        self.setCentralWidget(self.central_widget)
        self.text_editor.setFont(QtGui.QFont('Arial', 14))
        self.text_editor.document().contentsChanged.connect(self.text_changed)

 

    @QtCore.Slot()
    def text_changed(self):
        word = 'import'
        text_cursor_before_find_op = self.text_editor.textCursor()

        self.text_editor.moveCursor(QtGui.QTextCursor.MoveOperation.Start)
        found = self.text_editor.find(word)
        if found:
            text_cursor = self.text_editor.textCursor()
            text_cursor.setPosition(text_cursor.position())
            block = text_cursor.block()
            position_in_block = text_cursor.positionInBlock() - len(word)
            format_range = QtGui.QTextLayout.FormatRange()
            format_range.start = position_in_block
            format_range.length = len(word)
            format_range.format = self.text_editor.currentCharFormat()
            format_range.format.setForeground(QtGui.QColor('#FF0000'))
            
            formats = [format_range]
            block.layout().setFormats(formats)
            
            print(position_in_block,
                  repr(block.text()),
                  (format_range.start, format_range.length, format_range.format),
                  block.isValid(), block.isVisible(),
                  sep='\n', end='\n\n')
            // expand to the right 1 px.
            resizeEvent = QtGui.QResizeEvent(self.text_editor.size(), self.text_editor.size() + QtCore.QSize(1, 0))
            self.text_editor.resizeEvent( resizeEvent )
            // shrink to the left 1 px.
            resizeEvent = QtGui.QResizeEvent(self.text_editor.size(), self.text_editor.size() + QtCore.QSize(-1, 0))
            self.text_editor.resizeEvent( resizeEvent )
        self.text_editor.setTextCursor(text_cursor_before_find_op)

        
        


app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec()

Upvotes: 0

I have no idea why block.layout.setFormats() does not work.

But if your intention is to highlight the word "import", then you might want to use something like this.

from PySide6 import QtWidgets, QtCore, QtGui


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QtWidgets.QWidget(self)
        self.central_widget.setLayout(QtWidgets.QVBoxLayout(self.central_widget))
        self.text_editor = QtWidgets.QTextEdit(self.central_widget)
        self.central_widget.layout().addWidget(self.text_editor)
        self.setCentralWidget(self.central_widget)
        self.text_editor.setFont(QtGui.QFont('Arial', 14))
        self.text_editor.textChanged.connect(self.text_changed)

    @QtCore.Slot()
    def text_changed(self):
        word = 'import'
        text_cursor = self.text_editor.document().find(word)
        if text_cursor:
            extraSelection = QtWidgets.QTextEdit.ExtraSelection()
            extraSelection.cursor = text_cursor
            extraSelection.format = self.text_editor.currentCharFormat()
            extraSelection.format.setForeground(QtGui.QColor('#FF0000'))
            self.text_editor.setExtraSelections([extraSelection])  
        else:     
            self.text_editor.setExtraSelections([])  


app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec()

Of course this has some limitations, e.g. finding only the first occurrence in the whole document, not checking if the word is delimited by whitespace, keeping format after changing the word to something else etc. But you will need to resolve these yourself. Your original coude would have the same limitations.

Upvotes: 1

Related Questions