Position of cursor rectangle in QTextEdit (PySide6)

Question:

I need to get absolute cursor position (in pixels) in QTextEdit.

I try

from PySide6 import QtCore, QtWidgets, QtGui

class MyWidget(QtWidgets.QWidget):
    def __init__(self, parent):
        super().__init__(parent)

        self.text_edit = QtWidgets.QTextEdit(self)
        self.text_edit.setGeometry(10, 10, 100, 100)

        self.cursor = QtGui.QTextCursor(self.text_edit.document())
        self.cursor.insertText('abc yz abc')

        self.cursor = QtGui.QTextCursor(self.text_edit.document())
        self.cursor.setPosition(4)
        self.cursor.movePosition(QtGui.QTextCursor.MoveOperation.Right, QtGui.QTextCursor.MoveMode.KeepAnchor, 2)
        self.text_edit.setTextCursor(self.cursor)
        print(self.text_edit.cursorRect(self.cursor))
        print(self.text_edit.mapToGlobal(self.text_edit.cursorRect(self.cursor).topLeft()))


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    dialog = QtWidgets.QDialog()
    main = MyWidget(dialog)
    dialog.setGeometry(10,10,200,200)
    dialog.show()
    app.exec()

I wait that cursorRect(self.cursor) return rectangle that select yz chars, but it don’t.

Asked By: Alex

||

Answers:

There are two basic problems with your code. Firstly, from the documentation (my emphasis)…

returns a rectangle (in viewport coordinates) that includes the
cursor.

So the print statement should be…

print(self.text_edit.viewport().mapToGlobal(self.text_edit.cursorRect(self.cursor).topLeft()))

Secondly, you’re printing the cursor coordinates from the __init__ method so the widget isn’t visible and the real geometry isn’t known. Add a simple paintEvent implementation that shows the coords and schedules a further update…

def paintEvent(self, event):
    super(MyWidget, self).paintEvent(event)
    print(self.text_edit.viewport().mapToGlobal(self.text_edit.cursorRect(self.cursor).topLeft()))
    QtCore.QTimer.singleShot(100, self.update)

The code shown above is for demonstration purposes only and shouldn’t be considered for ‘real’ applications. It should, however, output the correct cursor coordinates.

A better example that make use of a simple 10Hz QTimer would be…

from PySide6 import QtCore, QtWidgets, QtGui

class MyWidget(QtWidgets.QWidget):
    def __init__(self, parent):
        super().__init__(parent)

        self.text_edit = QtWidgets.QTextEdit(self)
        self.text_edit.setGeometry(10, 10, 100, 100)

        self.cursor = QtGui.QTextCursor(self.text_edit.document())
        self.cursor.insertText('abc yz abc')

        self.cursor = QtGui.QTextCursor(self.text_edit.document())
        self.cursor.setPosition(4)
        self.cursor.movePosition(QtGui.QTextCursor.MoveOperation.Right, QtGui.QTextCursor.MoveMode.KeepAnchor, 2)
        self.text_edit.setTextCursor(self.cursor)
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.show_cursor_position)
        self.timer.start(100)

    def show_cursor_position(self):
        print(self.text_edit.viewport().mapToGlobal(self.text_edit.cursorRect(self.cursor).topLeft()))    

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    dialog = QtWidgets.QDialog()
    main = MyWidget(dialog)
    dialog.setGeometry(10,10,200,200)
    dialog.show()
    app.exec()
Answered By: G.M.