pythonqtqtexteditpyside6qtextcursor

Position of cursor rectangle in QTextEdit (PySide6)


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.


Solution

  • 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()