Gtk_text_buffer_get_text() issue

Hi, ALL,
Below is my code:

auto count = gtk_text_buffer_get_char_count( buffer );
char *deletedText = nullptr;
if( maxlen > 0 && count > maxlen )
{
    GtkTextIter offset, newend;
    auto startPos = gtk_text_iter_get_offset( end );
    gtk_text_buffer_get_iter_at_offset( buffer, &offset, startPos - 1 );
    gtk_text_buffer_get_iter_at_offset( buffer, &newend, ( startPos - 1 ) + ( count - maxlen ) );
    deletedText = gtk_text_buffer_get_text( buffer, &offset, &newend, true );
    auto newaddition = strstr( text, deletedText );

And below is the snippet from the gdb session:

Thread 1 “text” hit Breakpoint 1, au_insert_text_callback (buffer=0x5555557fc330,
end=0x7fffffffce00, text=0x5555559f8a30 “tuvwxyz”, len=7, win=0x5555557fc6e0)
at …/src/gtk/textctrl.cpp:573
573 gtk_text_buffer_delete( buffer, &offset, &newend );
(gdb) p count
$1 = 26
(gdb) p maxlen
$2 = 20
(gdb) p deletedText
$3 = 0x555555a3cae0 “z”
(gdb)

Is therfe a reason why the deletedText is “z” and not the “uvwxyz”?

Trying to implement the cut/paste for maximum text on the multiline text control…

Thank you.

Hi,

Are you inside the insert_text signal callback?
Are you connected with the AFTER flag?

If yes, the iter will be positioned at the end of the insert range, that may be why you see the last letter only.

@gwillems ,

Yes and yes.

So how do I fix it then?
Or don’'t rely on this and use the parameters passed to the handler?

But then - how do I d the bookkeeping for the iterator?

The code works fine when I simply type on the keyboard…

Thank you.

Can you please give some more context, like where end and startPos come from?

Also, where do you want to trim the extra text? inside the inserted part, or always at the end?

If always at the end, what about this?

if (maxlen > 0 && count > maxlen)
{
    GtkTextIter start, end;
    gtk_text_buffer_get_iter_at_offset (buffer, &start, maxlen - 1);
    gtk_text_buffer_get_end_iter (buffer, &end);
    deletedText = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
}

@gwillems ,

The signature of the handler is:

au_insert_text_callback(GtkTextBuffer *buffer,
GtkTextIter *end,
gchar *text,
gint len,
MyTextCtrl *win)

so the “end” comes from here.

The text needs to be trimmed at the end and the resulting text needs to be at the “maxlen”. I’m not sure how do you see the trimming inside the pasted text…

Ir maybe I’m just overthinking it?
I can reset the text inserted because I know its length and I know the maximu length alowed. So probably calling substr() will suffice,

All I need is to reset the iterator.

Will this work? If so - how do I do that?

Thank you.

Hmm… that sounds a bit confusing to me…
It’s not possible to modify the text received as parameter of the insert_text callback. All you can do is to trim the extras characters from the buffer itself afterwards.

I made a quick and dirty example, does that help?

#include <gtk/gtk.h>

#define MAX_TEXT_LENGTH  (20)

static void
on_insert_text (GtkTextBuffer *buffer, GtkTextIter *pos, gchar *text, gint len, gpointer user_data)
{
    int total = gtk_text_buffer_get_char_count (buffer);

    if (MAX_TEXT_LENGTH < total)
    {
        int trim_len = total - MAX_TEXT_LENGTH;
        char *trimmed_text;
        GtkTextIter start;

        gtk_text_buffer_get_iter_at_offset  (buffer, &start, gtk_text_iter_get_offset (pos) - trim_len);

        trimmed_text = gtk_text_buffer_get_text (buffer, &start, pos, TRUE);
        g_print("Delete text: '%s'\n", trimmed_text);
        g_free (trimmed_text);

        gtk_text_buffer_delete (buffer, &start, pos);
    }
}

static void
on_activate (GApplication *application, gpointer user_data)
{
    GtkWidget *window = gtk_application_window_new (GTK_APPLICATION (application));
    GtkWidget *view = gtk_text_view_new ();
    GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));

    g_signal_connect_after (buffer, "insert-text", G_CALLBACK (on_insert_text), NULL);

    gtk_window_set_child (GTK_WINDOW (window), view);
    gtk_window_present (GTK_WINDOW (window));
}

int
main (int argc, char *argv[])
{
    GtkApplication *app = gtk_application_new ("com.example.Application", G_APPLICATION_DEFAULT_FLAGS);

    g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);

    return g_application_run (G_APPLICATION (app), argc, argv);
}

@gwillems ,
You presume here that the text will be pasted at the end.

What if I try to past the text somewhere in the middle?

I think following should work:

if( maxlen > 0 && count > maxlen )
{
    GtkTextIter offset, newend;
    gtk_text_buffer_get_iter_at_offset( buffer, &offset, ( maxlen - 1 )) ;
    gtk_text_buffer_get_iter_at_offset( buffer, &newend, &end );
    deletedText = gtk_text_buffer_get_text( buffer, &offset, &newend, true );
    gtk_text_buffer_delete( buffer, &offset, &newend );
    end = offset;
    text = substr( text, count - maxlen );
    len = count - maxlen;

Meaning if I have text “abcdefghijklmnopqrs” and I want to paste the text “uvwxyz” beyween ‘c’ and ‘d’, all I should be able to paste is just ‘t’.

I hope that “end” here wull be pointed to the iterator of the “d” character and ‘s’ one.

Thank you.

yes, my example already works that way.

If you insert “tuvwxyz” beyween ‘c’ and ‘d’, then the end iter will point just before the ‘d’.

@gwillems
Is there an east way to fix the “text” parameter?
This is for the bookkeeping…

Thank you.

No, the text can’t be modified, it’s owned by the TextBuffer internally.

In your code above, I see you tried to modify it, but that can not work: you just overwrote the pointer, which is a local copy on the stack in the function, so that won’t impact the pointed data on TextBuffer side.

Same for the len parameter, it’s copied on the stack so modifying it will affect the local scope only.

Last point: when using g_signal_connect_after(), the new text has already been inserted in the buffer, the text parameter is just provided for reference, so even if threre was a way to change it, it would be too late to change the TextBuffer. The only reliable way is now to access the TextBuffer directly, with e.g. gtk_text_buffer_delete().

@gwillems ,
Understood thx.

I still can change the iterator though, right?

I suppose you can change the iterator, yes (it’s passed as pointer so can be assigned), but I never tried…

Note that after doing an insert or delete operation, all TextIters are invalidated, and must be re-created, typically from offsets or TextMarks.

@gwillems ,
Apparently I cn’t modify that either:

(text:32617): Gtk-WARNING **: 00:35:41.470: Invalid text buffer iterator: either the iterator is uninitialized, or the characters/pixbufs/widgets in the buffer have been modified since the iterator was created.
You must use marks, character numbers, or line numbers to preserve a position across buffer modifications.
You can apply tags and insert marks without invalidating your iterators,
but any mutation that affects ‘indexable’ buffer contents (contents that can be referred to by character offset)
will invalidate all outstanding iterators

So how will I be sure that everything is good when I delete the text?

Any suggestions?

I mean if I paste extra characters the iterator will point to the end of the paste. And when I delete the text it will still point to the same place.

And there is a code that rely on this behavior…

Thank you.

I’m sorry, I don’t understand why you want to modify the iter and how you did it…

Can you share some code?

Have you tested my example above, and does it fit your needs? Is there anything you want to improve?

@gwillems .
I put you handler into my code, compiled it and ran.

I continuoslu typed:

abcdefghijklmnopqrst

letter by letter and then I typed ‘u’.

The trimmed text was not become “u”, it is an empty string.

Thank you.


au_insert_text_callback(GtkTextBuffer *buffer,
                        GtkTextIter *end,
                        gchar *text,
                        gint len,
                        MyTextCtrl *win)
{
    GtkTextIter start = *end;
    auto count = gtk_text_buffer_get_char_count( buffer );
    char *trimmed_text;
    if( maxlen > 0 && count > maxlen )
    {
        GtkTextIter offset;
        int trim_len = count - maxlen;
        gtk_text_buffer_get_iter_at_offset( buffer, &offset, gtk_text_iter_get_offset( end ) - trim_len );
        trimmed_text = gtk_text_buffer_get_text (buffer, &start, end, TRUE);
        gtk_text_buffer_delete( buffer, &offset, end );
    }
}

Yes, there is a typo, you did get_text(&start, end) instead of get_text(&offset, end) :slight_smile:

@gwillems ,
Thank you. I made it work.

1 Like

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