Home > Software engineering >  How to prevent QPlainText area from scrolling to end of line?
How to prevent QPlainText area from scrolling to end of line?

Time:07-07

When appending text longer than the widget's width with word wrap set to NoWrap, how do I prevent the view from moving to the end of the line like in the second image?

What I want:

enter image description here

What I get:

enter image description here

Code for re-creating the above image(s):

from PySide6 import QtWidgets as qtw


class MainWindow(qtw.QMainWindow):

    def __init__(self):
        super().__init__()
        self._plain_text_edit = qtw.QPlainTextEdit()
        self._plain_text_edit.appendPlainText(' '.join(str(i) for i in range(1, 101)))
        self.setCentralWidget(self._plain_text_edit)
        self._plain_text_edit.setLineWrapMode(qtw.QPlainTextEdit.NoWrap)
        self.show()

app = qtw.QApplication()
mw = MainWindow()
app.exec()

This one example has a single line but the actual program will have multiple lines increasing throughout the run of the program. I'm using a QPlainText edit to display logs. I've tried moving the cursor, but I want the user to be able to select the text even when there's text being added and moving the cursor resets the selection every new line added.

Edit:
An MRE for a situation more similar to my actual program:

from PySide6 import QtWidgets as qtw
from PySide6 import QtCore


class MainWindow(qtw.QTabWidget):

    def __init__(self):
        super().__init__()
        self._plain_text_edit = qtw.QPlainTextEdit()
        self._plain_text_edit.setLineWrapMode(qtw.QPlainTextEdit.NoWrap)

        self._button = qtw.QPushButton("Press to add text.")
        self._button.clicked.connect(
            lambda:
                self._plain_text_edit.appendPlainText(
                    ' '.join(str(i) for i in range(1, 101))
                )
        )

        self.addTab(self._button, "Nothing")
        self.addTab(self._plain_text_edit, "edit")
        self.show()

app = qtw.QApplication()
mw = MainWindow()
app.exec()

CodePudding user response:

For some reason scrolling occurs only if you appendPlainText before widget become visible, so 0-second timer solves your problem.

from PySide6 import QtCore as qtc
qtc.QTimer.singleShot(0, lambda: self._plain_text_edit.appendPlainText(' '.join(str(i) for i in range(1, 101))))

Alternatively you can force first draw and spin event loop before adding text to achieve same effect.

self.show()
qtw.QApplication.processEvents()
self._plain_text_edit.appendPlainText(' '.join(str(i) for i in range(1, 101)))

In case of complex layout you can use eventFilter to unscroll horizontal scrollbar after show event:

from PySide6 import QtCore as qtc

class EventFilter(qtc.QObject):
    def eventFilter(self, obj, e):
        if e.type() == qtc.QEvent.Show:
            if isinstance(obj, qtw.QPlainTextEdit):
                qtc.QTimer.singleShot(0, lambda: obj.horizontalScrollBar().setValue(0))
                return False
        return super().eventFilter(obj, e)

class MainWindow(qtw.QMainWindow):
    def __init__(self):
        ...
        filter = EventFilter()
        self._plain_text_edit.installEventFilter(filter)
        self._filter = filter

CodePudding user response:

It is quite simple. Before adding the text, get the selection (anchor and cursor position) and after adding the text, just restore the selection.

from PySide6 import QtWidgets as qtw
from PySide6 import QtCore, QtGui

def appendPlainTextAndKeepCursor(text_edit, text):
    # store the current selection
    tc = text_edit.textCursor()
    anchor = tc.anchor()
    position = tc.position()

    # change the text
    text_edit.appendPlainText(text)

    # restore the selection
    tc.setPosition(anchor)
    tc.setPosition(position, QtGui.QTextCursor.KeepAnchor)
    text_edit.setTextCursor(tc)


class MainWindow(qtw.QTabWidget):

    def __init__(self):
        super().__init__()
        self._plain_text_edit = qtw.QPlainTextEdit()
        self._plain_text_edit.setLineWrapMode(qtw.QPlainTextEdit.NoWrap)

        self._button = qtw.QPushButton("Press to add text.")
        self._button.clicked.connect(lambda: appendPlainTextAndKeepCursor(self._plain_text_edit, ' '.join(str(i) for i in range(1, 101))))

        self.addTab(self._button, "Nothing")
        self.addTab(self._plain_text_edit, "edit")
        self.show()

app = qtw.QApplication()
mw = MainWindow()
app.exec()
  • Related