GTK+ Forums Forum Index GTK+ Forums
Discussion forum for GTK+ and Programming. Ask questions, troubleshoot problems, view and post example code, or express your opinions.
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Using GtkTextBuffer "mark-set" signal to update st

 
Post new topic   Reply to topic    GTK+ Forums Forum Index -> GTK+ Programming
Author Message
Micah Carrick
Never Seen the Sunlight


Joined: 21 Sep 2005
Posts: 505
Location: Portland, OR USA

PostPosted: Sun Dec 17, 2006 10:06 am    Post subject: Using GtkTextBuffer "mark-set" signal to update st Reply with quote

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: (Plaintext)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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: (Plaintext)
1
2
3
4
5
6
7
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: (Plaintext)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 */
}
Back to top
openldev
Never Seen the Sunlight


Joined: 21 Sep 2005
Posts: 387
Location: Fairfax, Virginia

PostPosted: Sun Dec 17, 2006 5:30 pm    Post subject: Reply with quote

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 ...
Back to top
Micah Carrick
Never Seen the Sunlight


Joined: 21 Sep 2005
Posts: 505
Location: Portland, OR USA

PostPosted: Sun Dec 17, 2006 7:16 pm    Post subject: Reply with quote

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?
Back to top
openldev
Never Seen the Sunlight


Joined: 21 Sep 2005
Posts: 387
Location: Fairfax, Virginia

PostPosted: Sun Dec 17, 2006 7:40 pm    Post subject: Reply with quote

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: (Plaintext)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 ...
Back to top
Micah Carrick
Never Seen the Sunlight


Joined: 21 Sep 2005
Posts: 505
Location: Portland, OR USA

PostPosted: Sun Dec 17, 2006 10:09 pm    Post subject: Reply with quote

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.
Back to top
openldev
Never Seen the Sunlight


Joined: 21 Sep 2005
Posts: 387
Location: Fairfax, Virginia

PostPosted: Sun Dec 17, 2006 10:25 pm    Post subject: Reply with quote

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 ...
Back to top
Display posts from previous:   
Post new topic   Reply to topic    GTK+ Forums Forum Index -> GTK+ Programming All times are GMT
Page 1 of 1

 


Powered by phpBB © 2001, 2005 phpBB Group
CodeBB 1.0 Beta 2
Protected by Anti-Spam ACP