Scrolling to the bottom textview gtkmm 3

Hi everyone,

I am trying to scroll to the bottom of a textview widget with the following;

Glib::RefPtr<Gtk::TextBuffer::Mark> m_endMark;
m_endMark = m_textArea.get_buffer()->create_mark(m_textArea.get_buffer()->end(), RIGHT_GRAVITY);
m_textArea.scroll_to(m_endMark);

there are no warnings or errors but the widget does not scroll to the bottom/end behaving same as if scroll_to is never applied. Anyone have any suggestions on what I might be missing ?

Is the end/mark visible initially? Then see the doc for that overload, bold mine:

Scrolls text_view so that mark is on the screen, by scrolling the minimal distance to get the mark onscreen, possibly not scrolling at all.

If you want to scroll to a point, controlling how it is aligned as a result, see the overloads taking align arguments.

If neither of these resolve it, please try to post a minimal runnable sample that people can use to investigate for you.

Scrolling is working as expected when I insert a line per button press. But when I insert let’s say for example 100 lines in a set_buffer scrolling does not happen at all

What insertions? What button press? Please show code or at least a complete outline of what you are doing; people cannot be expected to guess what might be happening if you don’t tell them. Again:

please try to post a minimal runnable sample that people can use to investigate for you.

Ok,

#include <iostream>
#include <string>
#include <gtkmm.h>

// Move mark to the right of newly added text (see below):
constexpr bool RIGHT_GRAVITY = false;

class MyWindow : public Gtk::ApplicationWindow
{

public:

    MyWindow()
    {
        m_button.signal_clicked().connect([this](){OnButtonPressed();});

        // Create a mark that "points" to the end of the buffer. This
        // mark will be updated accordinly as the buffer is modified:
        m_endMark = m_textArea.get_buffer()->create_mark(m_textArea.get_buffer()->end(), RIGHT_GRAVITY);

        m_scrolledWindow.add(m_textArea);

        m_layout.attach(m_scrolledWindow, 0, 0, 1, 1);
        m_layout.attach(m_button, 0, 1, 1, 1);

        add(m_layout);
    }

    void OnButtonPressed()
    {
        // Insert new line at the end of the Gtk::TextView:
        static int lineNumber = 0;
        m_textArea.get_buffer()->insert(m_textArea.get_buffer()->end(), "\n" + std::to_string(lineNumber) +" - test text");
        ++lineNumber;

        // Scroll down to the mark:
        m_textArea.scroll_to(m_endMark);
    }

private:

    Gtk::Grid m_layout;

    Gtk::ScrolledWindow m_scrolledWindow;
    Gtk::TextView m_textArea;

    Glib::RefPtr<Gtk::TextBuffer::Mark> m_endMark;

    Gtk::Button m_button{"Add line at the end..."};

};

int main(int argc, char* argv[]) 
{
    auto app = Gtk::Application::create(argc, argv, "test.test.test");

    MyWindow window;
    window.show_all();

    return app->run(window);
}

insertion is successful and scrolling occurs with this piece of code. Unfortunately when I paste something from clipboard or do a ;

m_RefTextBuffer1->insert(m_RefTextBuffer1->end(), something);
m_TextView.set_buffer(m_RefTextBuffer1);

to insert something to the end of the TextView scrolling does not happen

Of course it doesn’t. You only tell the text view to scroll to the end mark in your Button::clicked handler, so… why would it scroll to the end mark upon any other event? You should instead connect to TextBuffer::insert, TextBuffer::changed, and/or whatever else is appropriate for your needs.

When the button is pressed buffer is filled and

m_textArea.scroll_to(m_endMark);

is placed in void OnButtonPressed()where m_endMark points to

       m_endMark = m_textArea.get_buffer()->create_mark(m_textArea.get_buffer()->end(), RIGHT_GRAVITY);

dont’t you think scrolling must be done when there are multiple lines inserted at an instance ? I don’t see where there is a signal member on insert or changed for textbuffer and text view on the other hand

Yes, when the button is pressed. Which is not the same event as " when I paste something from clipboard or do am_RefTextBuffer1->insert" - is it? Nope :slightly_smiling_face:

I don’t see where there is a signal member on insert or changed for textbuffer and text view on the other hand

Look a little closer - link - and then browsing/searching the documentation could be your best friend :wink:

Now it’s clearer, thanks. But I could not resolve the issue still, when I check the docs I see there is a signal for insert-text which is ok. On the other hand I am filling the TextView with a ;

Glib::RefPtr<Gtk::TextBuffer> textbuffer1;

where I can not use ;

textbuffer1.insert-text().connect(...);

I believe I can not fix the syntax for the pointer origin of the textbuffer1 for the signal handler. Do you think it is possible if so please provide some insight or one-liner example on how to

You have to use the right syntax… If you have a RefPtr<SomeObject> then accessing members of SomeObject is done with operator-> as with any other pointer. And you need to use the correct signal getters, which again are clearly documented. So:

textbuffer1->signal_insert().connect(...)

In case I confused you by writing things like TextBuffer::insert, then to clarify: that is just a shorthand for a signal (two colons). Just like Widget:visible would be shorthand for a property (one colon). You still need to use the actual syntax of your chosen language in real code.

Sure, now I have a little bit more of some insight on how to correctly use signals and follow the documentation. I decided to go with signal_changed since it has less input variables than signal_insert for not to cause more complications. Strangely I am getting a segfault I can not spot the reason why … Here’s an example I mocked up for you to illustrate the origin of the error ;

#include <iostream>
#include <string>
#include <gtkmm.h>

// Move mark to the right of newly added text (see below):
constexpr bool RIGHT_GRAVITY = false;

class MyWindow : public Gtk::ApplicationWindow
{

public:

    MyWindow()
    {
        m_button.signal_clicked().connect([this](){OnButtonPressed();});
	textbuffer->signal_changed().connect([this](){test();}); 
        m_scrolledWindow.add(m_textArea);
        m_layout.attach(m_scrolledWindow, 0, 0, 1, 1);
        m_layout.attach(m_button, 0, 1, 1, 1);
        add(m_layout);
    }

    void OnButtonPressed()
    {
    textbuffer->insert(textbuffer->end(), "TEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\n");    
    m_textArea.set_buffer(textbuffer);
    }

void test(){
std::cout<<"test"<<std::endl;
}

private:

    Gtk::Grid m_layout;

    Gtk::ScrolledWindow m_scrolledWindow;
    
    Gtk::TextView m_textArea;
    
    Gtk::Button m_button{"Add lines at the end..."};
    
    Glib::RefPtr<Gtk::TextBuffer> textbuffer;
    
};

int main(int argc, char* argv[]) 
{
    auto app = Gtk::Application::create(argc, argv, "so.question.q66329582");

    MyWindow window;
    window.show_all();

    return app->run(window);
}

Compilation is successful without any warnings or errors but execution results in Segmentation fault, core dumped, any idea what might be causing this ?

You should use a debugger to determine where the fault occurs, then take it from there.

But I can easily see that you never set your Glib::RefPtr<Gtk::TextBuffer> textbuffer to point at anything… so it holds a null pointer, and a segfault is completely predictable.

A RefPtr is just that, a pointer, and you must ensure it points at some instance before using it. It’s not like non-pointer Gtk::Widget subclasses, which create/wrap the instance for you, so you have one even if calling the default constructor. Instead, a default-constructed RefPtr points at nothing, so you can’t dereference as you did. So you cannot just declare and use RefPtrs like ‘value’ Widgets and hope for the best; they are not the same.

Since the TextView default constructor creates and uses its own TextBuffer, you can just initialise your member RefPtr like so:

    Glib::RefPtr<Gtk::TextBuffer> textbuffer{ m_textArea.get_buffer() }

then it will actually point at an instance, i.e. the buffer created by the view, and so it will be valid to dereference.

But to be honest you probably don’t even need a member, can just do m_textArea.get_buffer()->FOO() whenever you need it.

If for any reason you wanted to create a separate TextBuffer, you would use an instance Gtk::TextBuffer::create(). All Glib::Object derived classes will provide a create() method that will return a valid, dereferencable RefPtr of their type (unless that class can only be retrieved from another class, factory, etc.)

Start here in the docs and that should explain most of what you could want to know about using RefPtrs. Beyond that, I suggest having a go at using gdb or another debugger, and that will empower you to fix your own bugs. :wink:

OK, with the guide of documentation I was able to debug the problem now it is working as expected, the sequence of the definitions in public section of the class was the issue. When there is an insert event on m_textArea string “test” is being printed on the console.

void test(){
std::cout<<"test"<<std::endl;
m_textArea.scroll_to(m_endMark);
}
scroll_to()

unfortunately still does not work

OK, from experimenting a bit, I think your ::changed handler must wait until GLib/GTK are idle before it scrolls, otherwise it conflicts with the standard TextView behaviour. You can try something like the below. See the docs on Glib::signal_idle() if you need more info about it.

In practice you should probably tweak which signals you useL ::changed fires for every keypress, so a user typing at the top is immediately scrolled to the bottom, away from what they’re typing… not very user-friendly. But maybe you can make it so. Start below. Good luck!

#include <iostream>
#include <string>
#include <gtkmm.h>

// Move mark to the right of newly added text (see below):
constexpr bool RIGHT_GRAVITY = false;

class MyWindow : public Gtk::ApplicationWindow
{
	Gtk::Grid m_layout;
	Gtk::ScrolledWindow m_scrolledWindow;
	Gtk::TextView m_textArea;
	Glib::RefPtr<Gtk::TextBuffer> m_textBuffer = m_textArea.get_buffer();
	Glib::RefPtr<Gtk::TextBuffer::Mark> m_endMark
		= m_textBuffer->create_mark("end", m_textBuffer->end(), RIGHT_GRAVITY);
	Gtk::Button m_button{"Add lines at the end..."};

public:
	MyWindow()
	{
		m_button.signal_clicked().connect( [this]{ OnButtonPressed(); } );
		m_textBuffer->signal_changed().connect( [this]{ test(); } );
		m_scrolledWindow.add(m_textArea);
		m_layout.attach(m_scrolledWindow, 0, 0, 1, 1);
		m_layout.attach(m_button, 0, 1, 1, 1);
		add(m_layout);
	}

	void OnButtonPressed()
	{
		m_textBuffer->insert(m_textBuffer->end(),
			"TEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\n"
			"TEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\nTEST\n"
			"TEST\nTEST\n");    
	}

	void test(){
		Glib::signal_idle().connect_once( [&]
		{
			std::cout << "test" << std::endl;
			m_textArea.scroll_to(m_endMark);
		} );
	}
};

int main(int argc, char* argv[]) 
{
	auto app = Gtk::Application::create(argc, argv, "so.question.q66329582");

	MyWindow window;
	window.show_all();

	return app->run(window);
}
1 Like

I guess I need to practice some fundamentals and spend more time in GTK documentation more than I think I need to. Your last reply did the trick, thank you mate! :innocent: :wink:

1 Like

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