Auto-scroll text view unless user moved view

I am trying to implement as TextView which automatically scrolls to display the latest added text unless there is any user scroll input.

Basically, the view auto-scrolls until the user does any scroll input or moves the slider bar, which has the effect of pausing the auto-scroll (text continues to be inserted).

When the user scrolls back to the bottom, auto-scroll resumes.

For the auto scrolling, I am using

end_iter = TextView.get_end_iter()
TextView.scroll_to_iter(end_iter, 0, False, 0, 0)

For detecting if the user has done any movement, I am trying to check if the end iterator is still visible with:

location = self.log_view.get_iter_location(iter)
visible = self.log_view.get_visible_rect()
if location.intersect(visible)[0]:
   text_view.scroll_to_iter(end_iter, ...)

However, location.intersect(visible) returns False with a resulting intersection rectable having all 0 values.

How would one go about implementing the functionality that I am after?

Thank you.

1 Like

Hi,

get_iter_location() returns a zero-width rectangle, so intersect() will always return False, because null areas can’t intersect with anything.

You can force the width to be at lease 1px:

location = self.log_view.get_iter_location(iter)
location.width = 1

Thanks for that hint.

I still can’t figure out how to determine if the user has changed the position of the text/scrollview. I can add a scroll event controller but that only deals with the mouse scroll. There doesn’t seem to be a way to determine this from the Adjustment (the value of the adjustment constantly changes because the view is auto-scrolled).

I suggest the other way around: try to detect the text insertion, e.g. with Gtk.TextBuffer::insert-text , and if the adjustment value+page>=upper then perform the scroll.

That is a interesting idea but unfortunately but unfortunately, it doesn’t work. The problem is that the volume of text that gets inserted is large enough to cause the upper value to jump way past the value + page sum on the first signal callback:

0.0 400.0 400.0 0.0
0.0 400.0 37808.0 37408.0
0.0 400.0 81504.0 81104.0

(values above are value, page_size, upper, upper - (value + page_size))

So, the text view ends up never actually scrolling.

You can calculate if we should scroll to bottom before adding the new text.

I implemented a similar behavior like this:

def add_to_terminal(line: str) -> None:
    """
    add line to termianl in logs dialog
    """
    terminal_text = get("LogsTextView")
    buffer = terminal_text.get_buffer()
    # Check if we're at the bottom.
    scrolled_window = terminal_text.get_parent()
    vadjustment = scrolled_window.get_vadjustment()
    value = vadjustment.get_value()
    upper = vadjustment.get_upper()
    # Allow a third of window to still treat it as "at bottom".
    page_size = vadjustment.get_page_size()
    buffer_zone = page_size / 3
    at_bottom = (value + page_size + buffer_zone) >= upper
    # Insert text
    buffer.insert(buffer.get_end_iter(), f"\n{line}\n")
    # If we were at the bottom before, scroll to the new bottom.
    if at_bottom:
        mark = buffer.create_mark(None, buffer.get_end_iter(), False)
        terminal_text.scroll_to_mark(mark, 0.0, False, 0.0, 0.0)

My issue is that the amount of text being inserted is very large. So, in the first iteration, checking value, upper and page_size works:
value = 0, page_size = 400, and upper = 0. However, on the next iteration, upper is in the order of 15K. So the auto scroll does not work.

May be, I should split the text to be inserted into lines and add them line-by-line but I fear that will make things much slower.

Vte (the terminal widget) has a similar feature, maybe have a look there how it’s done?
There is a flag m_scroll_on_output for that, checked here.

Another possibility is to use a “pause” toggle button.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.