4

I have a popup that only contains a QTextEdit, it has a lot of text in it, a lot of lines. I want it to scroll to a certain line in the QTextEdit on show(). So that the line I want is at the top.

Code snippet:

editor = QtGui.QTextEdit()
# fill the editor with text
# set the scroll to nth line
editor.show()

How can I achieve that?

Update

I've managed to get it to show the nth line at the bottom:

cursor = QtGui.QTextCursor(editor.document().findBlockByLineNumber(n))
editor.moveCursor(QtGui.QTextCursor.End)
editor.setTextCursor(cursor)

For example for n=25 I get:

_______________________
.
.
.
.
25th line
_______________________

But I need it to be at the top...

Bob Sacamano
  • 699
  • 15
  • 39
  • You need to operate vertical scroll bar. Knowing the page size and how many lines in the text edit you can calculate how much you can scroll the text edit up. See somewhat related: http://stackoverflow.com/questions/4939151/how-to-program-scrollbar-to-jump-to-bottom-top-in-case-of-change-in-qplaintexted – Alexander V Nov 10 '16 at 18:33

1 Answers1

4

You almost have it. The trick is to move the current cursor to the bottom first, and then reset the cursor to the target line. The view will then automatically scroll to make the cursor visible:

editor.moveCursor(QtGui.QTextCursor.End)
cursor = QtGui.QTextCursor(editor.document().findBlockByLineNumber(n))
editor.setTextCursor(cursor)

By extension, to position the cursor at the bottom, move the current cursor to the start first:

editor.moveCursor(QtGui.QTextCursor.Start)
...

Here's a demo script:

from PyQt4 import QtCore, QtGui

class Window(QtGui.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.edit = QtGui.QTextEdit(self)
        self.edit.setPlainText(
            '\n'.join('%04d - blah blah blah' % i for i in range(200)))
        self.button = QtGui.QPushButton('Go To Line', self)
        self.button.clicked.connect(self.handleButton)
        self.spin = QtGui.QSpinBox(self)
        self.spin.setRange(0, 199)
        self.spin.setValue(50)
        self.check = QtGui.QCheckBox('Scroll Top')
        self.check.setChecked(True)
        layout = QtGui.QGridLayout(self)
        layout.addWidget(self.edit, 0, 0, 1, 3)
        layout.addWidget(self.button, 1, 0)
        layout.addWidget(self.spin, 1, 1)
        layout.addWidget(self.check, 1, 2)
        QtCore.QTimer.singleShot(0, lambda: self.scrollToLine(50))

    def scrollToLine(self, line=0):
        if self.check.isChecked():
            self.edit.moveCursor(QtGui.QTextCursor.End)
        else:
            self.edit.moveCursor(QtGui.QTextCursor.Start)
        cursor = QtGui.QTextCursor(
            self.edit.document().findBlockByLineNumber(line))
        self.edit.setTextCursor(cursor)

    def handleButton(self):
        self.scrollToLine(self.spin.value())
        self.edit.setFocus()

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 100, 400, 300)
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Nice solution. One thing: this all works of course only if there are enough lines left (total number of lines m minus desired line n must be equal or greater to number of displayed lines in the viewport). Also it may not be necessary to scroll to the bottom, just scroll far enough down (scroll to line n + x (number of displayed lines in the viewport) for example). – NoDataDumpNoContribution Nov 10 '16 at 20:35
  • @Trilarion. I don't think `QTextEdit` has the facility to scroll beyond the last line like some editor controls do (e.g. Scintilla), so the first point seems moot. I also don't see any noticeable performance impact from moving the cursor to the end first (I tested with a 300k-line file). – ekhumoro Nov 10 '16 at 20:59
  • This solution sounds good, but it doesn't work for me. The line with the cursor is shown last in the QTextEdit. Are there any further requirements involved? (I'm using PyQt 4.8 on python 2.7) – ImportanceOfBeingErnest Nov 10 '16 at 22:19
  • @ImportanceOfBeingErnest. It works fine for me using PyQt-4.7, PyQt-4.9.5 and PyQt-4.11.4 with Python-2.7. Are you sure you're using `QTextCursor.End`? – ekhumoro Nov 10 '16 at 22:37
  • @ekhumoro I just copied the three lines from above and put them into the __init__ method of a class. And that exactly seems to be the problem. So, the above code will work **but only after the Widget has been painted.** In contrast, the method from the question (setting cursor to line at bottom of TextEdit) will also work before painting. – ImportanceOfBeingErnest Nov 10 '16 at 22:49
  • @ImportanceOfBeingErnest. I added a fully working demo script. – ekhumoro Nov 10 '16 at 23:13
  • @ekhumoro That works great. I do have a different problem of resizing a QTextEdit, http://stackoverflow.com/questions/40136655/pyqt-manually-resizing-menu-widget-does-not-resize-its-content Maybe you want to have a look at it? – ImportanceOfBeingErnest Nov 15 '16 at 08:57