I am working on an app with a text editor based on QTextEdit.
I try to achieve a behavior which is similar to Word:
When you move the text cursor to any position in the text the QTextCharFormat of the QTextCursor is changed to the CharFormat of the character right before the cursor, and the Buttons for Bold/Italic/Underlined, the QFontComboBox and the QComboBox for the point size of the editor-widget are checked and set accrodingly.
Originally I connected the signal cursorPositionChanged() to the method lastCharFormat which handles the character format of the character before the cursor by calling charFormat(). But this signal turned out to be unsuitable because it emits every time I type in a character. So I used keyPressEvent() to catch inputs of the arrow keys and emit a custom signal.
I haven't implemented the text cursor position change by mouse because I noticed a problem with the text cursor position when moving the cursor with the arrow keys. When I move the cursor from the end of the document to the start the cursor return 1 but when I move the cursor one position to the right it returns 0.
Edit: Found out that position() does not return the current position but the position before.
This is a problem because I assumed the start position of the text editor to be 1 and I had to implement a check because returning the properties of the QTextCharFormat at the start position crashes the app and also if there's no text. Apparently this is due to getting stuck in a loop. Anyway the main problem is the strange behavior of the text cursor position.
Why does QTextCursor behaves this way? Is it a bug? And how can I get the properties of QTextCharFormat of the character right before the text cursor when the user moves the cursor with the arrow keys or the mouse without crashing the app?
Here is part of the code of the custom text editor element that inherits from QTextEdit.
class TextElement(QTextEdit):
cursorMoved = pyqtSignal()
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.editor = parent.editor
self.connectSignals()
def connectSignals(self) -> None:
self.cursorMoved.connect(self.lastCharFormat)
def keyPressEvent(self, e: QKeyEvent) -> None:
# Cursor Changed by Key
if e.key() == Qt.Key.Key_Up or e.key() == Qt.Key.Key_Left or e.key() == Qt.Key.Key_Down or e.key() == Qt.Key.Key_Right:
self.cursorMoved.emit()
return super().keyPressEvent(e)
def lastCharFormat(self) -> None:
cursor = self.textCursor()
# Printing text cursor position to test behavior
print(cursor.position())
if cursor.position() > 1:
tformat = cursor.charFormat()
# From this point the app crashes if position is the start position (0 or 1)
family = tformat.fontFamily()
size = tformat.fontPointSize()
if tformat.fontWeight() == 700:
bold = True
else:
bold = False
italic = tformat.fontItalic()
underlined = tformat.fontUnderline()
print(f"Char format before cursor: FontFamily: {family}, Size: {size}, Bold: {bold}, Italic: {italic}, Underlined: {underlined}")
# This function sets the buttons, QFontComboBox and QComboBox with the point sizes of the editor accordingly
self.editor.setFontProps(family, size, bold, italic, underlined)
I tried to set the text cursor position in the keyPressEvent but this didn't correct the text cursor behavior.
QTextCursor's position is a 0-based index, meaning the first position is 0, not 1. In your code, when you check if the position is greater than 1, you are checking if it's the second position or later. This causes the code to crash when the cursor is at position 0, because you are trying to access the character format of a non-existing character.
To fix the issue, you should change the condition in the lastCharFormat method to check if the position is greater than or equal to 0 instead of greater than 1. This will allow the code to access the character format of the character right before the cursor even when the cursor is at the start position.
Also, in the keyPressEvent method, you are checking for arrow key presses to emit the cursorMoved signal. This is a good approach, but you should also consider emitting the signal for other types of cursor movement, such as clicking the mouse to move the cursor or using the mouse wheel to scroll. You can do this by connecting to the cursorPositionChanged signal and emitting the cursorMoved signal in the slot connected to cursorPositionChanged.
This way, the lastCharFormat method will be called whenever the cursor position changes, whether it's due to a key press, mouse click, or mouse wheel scroll.