Standard
Standard

Reputation: 1512

Scroll to bottom of Scrollable if content changes in Python GTK

I'm building a GUI which has a scrollable element, which holds an Textview. I'm using a Textbuffer to add Text to the textview; it works good but when the content gets to big, the scrollbar is staying in its place. I created my GUI with glade.

I read the docs but I couldn't find a method like scrollToBottom() or anything else.

So how can I make sure that the scrollable is always at the bottom when new text is outputted?

Upvotes: 1

Views: 1796

Answers (1)

lb90
lb90

Reputation: 958

To scroll in GtkTextView you have to use markers in GtkTextBuffer. GtkTextMark objects are auto-updating, so just create the marker once when you create the TextBuffer.

  1. Create the GtkTextBuffer object and a GtkTextMark
  2. Create the GtkTextView
  3. Anytime you append text you can call gtk_text_view_scroll_to_mark to scroll at the end

Here's how to do it:

C

GtkTextView *text_view;
GtkTextMark *text_mark_end;

void create_text_view () {
  text_view = gtk_text_view_new ();

  /* get the text buffer */
  GtkTextBuffer *text_buffer;
  text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));

  /* create an auto-updating 'always at end' marker to scroll */
  GtkTextIter text_iter_end;
  gtk_text_buffer_get_end_iter (text_buffer, &text_iter_end);
  text_mark_end = gtk_text_buffer_create_mark (text_buffer,
                                               NULL,
                                               &text_iter_end,
                                               FALSE);
}

void append_text(const gchar *text) {
  if (text) {
    /* get the text buffer */
    GtkTextBuffer *text_buffer;
    text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));

    /* get an end iter */
    GtkTextIter text_iter_end;
    gtk_text_buffer_get_end_iter (text_buffer, &text_iter_end);

    /* append text */
    gtk_text_buffer_insert (text_buffer, &text_iter_end);

    /* now scroll to the end using marker */
    gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view),
                                  text_mark_end,
                                  0., FALSE, 0., 0.);
  }
}

void destroy_text_view () {
  g_object_unref (text_mark_end);
  gtk_widget_destroy (text_view);
}

Python

#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class TextView(Gtk.TextView):
  def __init__(self):
    Gtk.TextView.__init__(self)
    # create mark for scrolling
    text_buffer = self.get_buffer()
    text_iter_end = text_buffer.get_end_iter()
    self.text_mark_end = text_buffer.create_mark("", text_iter_end, False)

  def append_text(self, text):
    # append text
    text_buffer = self.get_buffer()
    text_iter_end = text_buffer.get_end_iter()
    text_buffer.insert(text_iter_end, text)
    # now scroll using mark
    self.scroll_to_mark(self.text_mark_end, 0, False, 0, 0)

class Window(Gtk.Window):
  def __init__(self):
    Gtk.Window.__init__(self)
    self.set_title('Test GtkTextView scrolling')
    self.set_default_size(400, 300)

    self.grid = Gtk.Grid()
    self.scrolled_win = Gtk.ScrolledWindow()
    self.text_view = TextView()
    self.button = Gtk.Button(label='Append text')
    self.scrolled_win.set_hexpand(True)
    self.scrolled_win.set_vexpand(True)

    self.scrolled_win.add(self.text_view)
    self.grid.add(self.scrolled_win)
    self.grid.add(self.button)
    self.add(self.grid)

    self.connect('destroy', Gtk.main_quit)
    self.button.connect('clicked', self.on_button_clicked)
    self.show_all()

  def on_button_clicked(self, widget):
    self.text_view.append_text('Hello\n' * 5);

if __name__ == '__main__':
  win = Window()
  Gtk.main()

Note:

There is also a gtk_text_view_scroll_to_iter function. This is not really useful because GtkTextView uses an animated scrolling (the animated scrolling can be seen for example with gedit with PAGE up/down). Unlike marker based scrolling, iter based scrolling does not play well with the animated scrolling capability of GtkTextView:

https://developer.gnome.org/gtk3/stable/GtkTextView.html#gtk-text-view-scroll-to-iter

Note that this function uses the currently-computed height of the lines in the text buffer. Line heights are computed in an idle handler; so this function may not have the desired effect if it’s called before the height computations. To avoid oddness, consider using gtk_text_view_scroll_to_mark() which saves a point to be scrolled to after line validation.

Upvotes: 3

Related Questions