Search code examples
pythonpyqt5qtexteditcursor-position

In Qt5/PyQt5 how do I restore exact visible area and cursor position of QTextEdit?


This question has been asked before but only regarding the cursor position as it seems..

I've implemented some kind of auto-load-on-change text editor based on QTextEdit. Problem is: when I apply the newly loaded text using

self.my_editor.setPlainText(new_text)

the cursor will jump to position 0, which is very uncomfortable.

Of course I'm not the only one who feels this way so people already asked how they could restore the cursor after applying the new text, e.g. here: https://www.qtcentre.org/threads/13933-Restoring-cursor-position-in-QTextEdit

But the answers I found only cover the position of the text cursor itself.

For example this code would save the cursor, set the new text and restore the cursor:

c = self.my_editor.textCursor()
p = c.position()
self.my_editor.setPlainText(new_text)
c.setPosition(p)
self.my_editor.setTextCursor(c)

But now effectively the editor would scroll to position 0 and then down again to be able to show the cursor at the restored position. So visually the editor jumps about (which is still better than losing the cursor, though)

Is there a way to also store the visual area of the QTextEdit instance? Of course in case the text content has really changed it's all but trivial to minimize the visual jumping but shouldn't it be possible to see no jumping at all in case the text has change behind the cursor?


Solution

  • Assuming that the contents are always almost the same, you can store the values of the scroll bars, and then set them after updating the text:

            c = self.my_editor.textCursor()
            p = c.position()
            hPos = self.my_editor.horizontalScrollBar().value()
            vPos = self.my_editor.verticalScrollBar().value()
            self.my_editor.setPlainText(new_text)
            c.setPosition(p)
            self.my_editor.horizontalScrollBar().setValue(hPos)
            self.my_editor.verticalScrollBar().setValue(vPos)
            self.my_editor.setTextCursor(c)
    

    The order of scroll bar and cursor reset depends on your needs: if the cursor could be outside the visible viewport, you might prefer to set the cursor first and then restore the scroll bars.

    Finally, if the contents could change a lot more, you should use ratios based on the scroll bars maximum values:

            try:
                hBar = self.my_editor.horizontalScrollBar()
                hPos = hBar.value() / hBar.maximum()
            except ZeroDivisionError:
                hPos = 0
            try:
                vBar = self.my_editor.verticalScrollBar()
                vPos = vBar.value() / vBar.maximum()
            except ZeroDivisionError:
                vPos = 0
            self.my_editor.setPlainText(new_text)
            c.setPosition(p)
            self.my_editor.setTextCursor(c)
            hBar.setValue(hPos * hBar.maximum())
            vBar.setValue(vPos * vBar.maximum())
    

    Note: the maximum values must be used dynamically, as the range could change when the text changes.