GTK+ Forums

Discussion forum for GTK+ and Programming. Ask questions, troubleshoot problems, view and post example code, or express your opinions.
It is currently Sat Nov 01, 2014 9:08 am

All times are UTC




Post new topic Reply to topic  [ 6 posts ] 
Author Message
 Post subject: Using GtkTextBuffer "mark-set" signal to update st
PostPosted: Sun Dec 17, 2006 10:06 am 
Offline
Never Seen the Sunlight

Joined: Wed Sep 21, 2005 12:07 am
Posts: 563
Location: Portland, OR USA
Hey, just thought I'd post this so other people can find it if they run into the same/similar issues. Anything to help me understand this a bit better is much appreciated.

I was using the "mark-set" signal for GtkTextBuffer (of a GtkSourceBuffer) to display the current line and column of the GtkSourceView widget. The code I was trying to use was:

Code:
void
on_mark_set (GtkTextBuffer *textbuffer, GtkTextIter *arg1, GtkTextMark *arg2, gpointer user_data)
{
        Document *d = (Document*)user_data;
        update_statusbar (d);
}
               
void
update_statusbar (Document *current_document)
{
        gint            column;
        gint            line;
        GtkTextMark     *mark = NULL;
        GtkTextIter     iter;
           
        /* get iter at the insertion point */     
        mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (current_document->buffer));
        gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (current_document->buffer),
                        &iter, mark);

        /* get current line */
        line = gtk_text_iter_get_line (&iter) + 1;

        /* get current column */
        column = gtk_text_iter_get_line_offset (&iter) + 1;

        /* ... update statusbar here */
}


This seemed to work at first. However, if you type too fast (such as mashing a bunch of keys at once or hitting two keys in rapid succession) I'd get a nice crash:

Code:
Gtk-WARNING **: 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

Gtk-CRITICAL **: gtk_text_buffer_get_iter_at_mark: assertion `GTK_IS_TEXT_MARK (mark)' failed


So, I figured that the update_statusbar function was being multi-threaded? Calls using "iter" and/or "mark" were crashing because another instance of the function had changed them... or the mark had changed in the widget? Still trying to understand this better. But in any case, I figured I needed a way to update the status bar ONLY if the user had stopped typing long enough to update the statusbar without them typing and thus updating the statusbar again.

This is the part I'm "ify" on as I'm still not a seasoned pro with all this GTK+ stuff (Not until I get Foundations of GTK+ Development anyway). Don't know if my assumptions as to why I encountered this problem are correct.

I got it working using g_idle_add as shown below:

Code:
void
on_mark_set (GtkTextBuffer *textbuffer, GtkTextIter *arg1, GtkTextMark *arg2, gpointer user_data)
{
        Document *d = (Document*)user_data;
        static guint    idle_id;
       
        if (idle_id != 0)
        {
                g_source_remove (idle_id);
                idle_id = 0;
        }
        idle_id = g_idle_add((GSourceFunc)update_statusbar, d);
}
               
gboolean
update_statusbar (Document *current_document)
{
        gint            column;
        gint            line;
        GtkTextMark     *mark = NULL;
        GtkTextIter     iter;

        gdk_threads_enter ();
           
        /* get iter at the insertion point */     
        mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (current_document->buffer));
        gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (current_document->buffer),
                        &iter, mark);

        /* get current line */
        line = gtk_text_iter_get_line (&iter) + 1;

        /* get current column */
        column = gtk_text_iter_get_line_offset (&iter) + 1;
                  
        gdk_threads_leave ();

        /* ... update statusbar here */
       
        return FALSE;   /* remove idle */
}

_________________
Micah Carrick - Forum Administrator
http://www.micahcarrick.com


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 17, 2006 5:30 pm 
Offline
Never Seen the Sunlight

Joined: Wed Sep 21, 2005 3:07 am
Posts: 384
Location: Fairfax, Virginia
Ok, so let's take this one thing at a time. Your original code was crashing when you were typing quickly and that is because calls to update_statusbar() were overlapping. Remember that when you make any change to a GtkTextBuffer, _all_ iterators are immediately invalidated. You can reuse the object, but you must reinitialize it to the desired location.

Using a timeout or idle function is ok, but if the user is not typing, processing is wasted. A better solution would be to use GtkTextView's move-cursor signal. You can use g_signal_stop_emission_by_name() to stop the signal from being emitted while the function is in use. This is a much better signal to connect to for your purposes. One other thing I don't understand is why you aren't sending the supplied GtkTextIter to the update_statusbar() function since it will already point to the mark ...

As a last note, you need to be very careful when using timeouts and idle functions because they can cause a lot of overhead in the way of processing. The nice thing about both of these is that the function will never be called again until the previous call returns, which is why it didn't crash your application. But then again, the function could be called thousands of times when the user isn't even doing anything with the text view. It is a good idea in almost every case to simply find a signal since you now have a way to stop signal emission.

And another thing with timeouts that I would like to mention for the benefit for everyone. Never ever ever use them to count time. If you set the timeout to cycle every 1 second and your function takes 4 milliseconds to process, the timeout will actually cycle every 1004ms since it continues counting from when the function returns. When trying to keep track of time, always use GTimer provided by GLib. I just figured this would be a good thing for people to know ...

_________________
Andrew Krause

Foundations of GTK+ Development: Buy now for only $31.49!


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 17, 2006 7:16 pm 
Offline
Never Seen the Sunlight

Joined: Wed Sep 21, 2005 12:07 am
Posts: 563
Location: Portland, OR USA
Thanks for that information! It's very hepful. I'll revisit this issue (I knew you'd have some insight).

As for the "move-cursor" signal, that was my first idea. I was using that. It had 2 issues...

1. The API says "Applications should not connect to it, but may emit it with g_signal_emit_by_name() if they need to control scrolling programmatically" which I wasn't sure what that means exactly and...

2. It doesn't trigger when the cursor is moved via mouse click /programatic insertion, only key press (I was just gonna grab on to some other signals).

Quote:
Using a timeout or idle function is ok, but if the user is not typing, processing is wasted.

I'm trying to understand this. "If the function returns FALSE it is automatically removed from the list of event sources and will not be called again." I was assuming that it would only be called once since I'm returning FALSE from the function and removing the source from the main context in the callback function if it hasn't yet been executed?

_________________
Micah Carrick - Forum Administrator
http://www.micahcarrick.com


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 17, 2006 7:40 pm 
Offline
Never Seen the Sunlight

Joined: Wed Sep 21, 2005 3:07 am
Posts: 384
Location: Fairfax, Virginia
As for #1, that's just so people don't start doing stupid things. It's just that with huge buffers, it can cause things to slow down dramatically if people start messing too much. Since it only catches key-presses (I totally forgot that), you can just connect to button-press-event as well & check to make sure that button 1 was pressed.

And yes, you are correct that the idle function will be removed by returning FALSE, but it is quite an overhead to add, when you are connecting the idle function every time the mark is set & then removing it after one call. Why don't you just stop mark-set from being emitted while the function is currently processing? With the following possibly:

Code:
void
on_mark_set (GtkTextBuffer *textbuffer,
             GtkTextIter *iter,
             GtkTextMark *mark,
             Document *d)
{
  static gboolean active = FALSE;
  gint column, line;

  if (active)
    return;

  active = TRUE;

  line = gtk_text_iter_get_line (iter) + 1;
  column = gtk_text_iter_get_line_offset (iter) + 1;
  /* ... update GtkStatusbar here ... */

  active = FALSE;
}


I think that should do the trick, or you can actually stop it from being emitted at all with the function I gave before. This might be a faster solution, though I'm not really sure. Try it out & let me know ...

Also, you can cast Document* in the callback function as long as you use G_CALLBACK() when connecting the signal with g_signal_connect() as shown above. It just saves on typing ...

_________________
Andrew Krause

Foundations of GTK+ Development: Buy now for only $31.49!


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 17, 2006 10:09 pm 
Offline
Never Seen the Sunlight

Joined: Wed Sep 21, 2005 12:07 am
Posts: 563
Location: Portland, OR USA
Quote:
you are connecting the idle function every time the mark is set & then removing it after one call.

Ah... yes I see.

Quote:
Why don't you just stop mark-set from being emitted while the function is currently processing?

I need the most recent call to be executed. If I prevent "mark-set" from updating the statusbar when another instance is updating the status bar, then it's could potentially display the wrong information. That is, a user presses "a" and "d" (hypothetical) at about the same time, but with "b" slightly after "a". If the handler when "a" is still running when "b" gets there thus preventing "b" from updating status bar, the end result might be "Ln 1, Col 2" when it should read "Ln 1, Col 3". Know what I mean?

I also noticed that the "mark-set" signal seems to get called 4 times for a single mouse click.

_________________
Micah Carrick - Forum Administrator
http://www.micahcarrick.com


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 17, 2006 10:25 pm 
Offline
Never Seen the Sunlight

Joined: Wed Sep 21, 2005 3:07 am
Posts: 384
Location: Fairfax, Virginia
Another possible thing to do is connect to the signal "notify::cursor-position", which will monitor the GtkTextBuffer cursor position. Then, just retrieve the iterator with gtk_text_buffer_get_iter_at_offset(). It _should_ process fast enough if you do it this way ...

_________________
Andrew Krause

Foundations of GTK+ Development: Buy now for only $31.49!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group