Question

I've researched this extensively but haven't found a satisfactory solution yet:

How do I append text at the end of QTextEdit widget without triggering a scroll to the bottom of the widget when either of these conditions is met:

  • The user has selected some text.
  • The user has scrolled away from the bottom.

(In all other cases, a scroll to the bottom of the QTextEdit widget should be triggered.)

Here is the code I'm currently using to append text at the bottom of a QTextEdit widget:

const QTextCursor old_cursor = widget.textCursor();

widget.moveCursor(QTextCursor::End);
widget.textCursor().insertText(text);

if (old_cursor.hasSelection())
    widget.setTextCursor(old_cursor);
else widget.moveCursor(QTextCursor::End);

This partially takes care of condition 1: the problem is that the view will still scroll until only the last line of the selection is visible, at which point it will indeed stop scrolling.

Condition 2 is not taken care of at all: some posts suggest to save the position of the vertical scrollbar and restore it after the text was appended, however I don't think this is correct since the scrollbar should move upward when text is appended, even though the view stays still.

Note that I'm using QTextCursor::insertText() instead of QTextEdit::append() because I need to adjust the color of the text being appended, regardless of whether the user has selected text or not.


Update: Here is the code I ended up with, thanks to Pavel's answer:

const QTextCursor old_cursor = widget.textCursor();
const int old_scrollbar_value = widget.verticalScrollBar()->value();
const bool is_scrolled_down = old_scrollbar_value == widget.verticalScrollBar()->maximum();

// Move the cursor to the end of the document.
widget.moveCursor(QTextCursor::End);

// Insert the text at the position of the cursor (which is the end of the document).
widget.textCursor().insertText(text);

if (old_cursor.hasSelection() || !is_scrolled_down)
{
    // The user has selected text or scrolled away from the bottom: maintain position.
    widget.setTextCursor(old_cursor);
    widget.verticalScrollBar()->setValue(old_scrollbar_value);
}
else
{
    // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom.
    widget.moveCursor(QTextCursor::End);
    widget.verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
Was it helpful?

Solution

Saving and restoring scrollbar position is quite correct and works perfectly. When document's length is increased, scrollbar's maximum value is increased. But its value is still equal to number of pixels above the viewport. So when you add contents to the document and set the same scrollbar value repeatadly, scrollbar's handle will move to the top, but the content will remain immobile.

It seems that you already know how to check if the user has selected some text. To check if the user has scrolled away from the bottom, you should simply compare vertical scrollbar's value with its maximum.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top