Reputation: 48
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
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
Reputation: 8057
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