SOLVED - sortof [was Re: "mark_set" callback fires 3 or 4 times when using arrow keys/mouse in textview?}



On 12/22/2015 05:13 PM, David C. Rankin wrote:
   How can I limit the the execution of the 'on_mark_set' callback to a single
call regardless of whether there is data being entered or if cursor is being
moved with the arrow-keys or mouse? Any help will be greatly appreciated.


OK, I have a solution that is probably not as elegant as desired. Delving into the "mark_set" signal and multiple firing of the callback opened a small can of worms regarding a number of considerations associated with GtkTextMark and GtkTextIter relationships.

The problem can be summarized as follows:

(1) the proper callback prototype to handle the "mark_set" signal is:

    void on_mark_set (GtkTextBuffer *buffer, GtkTextIter *iter,
                      GtkTextMark *mark, context *app)

(2) there are multiple text marks at each iter location.

(3) when responding to the "mark_set" signal, the 'mark' passed to the callback can be any of the marks at the "iter" location which are passed in "no particular order" and are passed differently depending on whether normal text is being entered, arrow-keys are pressed, or whether the mouse is clicked to reposition to "insert" cursor mark.

(4) This prevents a simple comparison between the "mark" parameter and gtk_text_buffer_get_insert (buffer) alone from being used to determine whether to respond to a "mark_set" signal or not.

The short version of a solution to prevent responding to multiple "mark_set" signals is as follows. (the callback is still fired, but you can test between 'current' and 'new' line:col locations responding only when the values differ)

void on_mark_set (GtkTextBuffer *buffer, GtkTextIter *iter,
                  GtkTextMark *mark, context *app)
{
    gint line, col;

    line = gtk_text_iter_get_line (iter);
    col = gtk_text_iter_get_line_offset (iter);

    if (line == app->line && col == app->col) return;

    app->line = line;
    app->col = col;

    g_print (" line: %3d col: %d\n", app->line + 1, app->col + 1);

    if (buffer) {}
    if (mark) {}
}

Now for the rest of the story... I am normally reasonably adept at finding this type of information myself in the documentation or via the web without having to ask, but in this case there simply isn't much in the way of details on how, what or in what order "mark_set" callback parameters are passed each time the callback is fired. I'll leave what I found here to save the next person a bit of time.

Each time the "mark_set" signal is generated, there can be multiple marks at any given iter *location*. In the case of normal input (e.g. 'a', 'b', etc...) the mark passed to the on_mark_set() callback is not necessarily the "insert" mark, but is apparently simply the last of the marks present at that iter *location*. (In each case below an anonymous mark is passed as a result of normal text input) The list of marks at any given iter position can be found by the GSList of marks returned by gtk_text_iter_get_marks (iter). (*note:* the marks in the list returned are in *no particular* order -- which is probably the basis for this whole issue to begin with. See: https://developer.gnome.org/gtk2/stable/GtkTextIter.html#gtk-text-iter-get-marks) For example, you can examine the marks with the following debug code:

    void on_mark_set (GtkTextBuffer *buffer, GtkTextIter *iter,
                    GtkTextMark *mark, context *app)
    {
        gint line, col;

    #ifdef DEBUG
        g_print ("  mark: %p  - gtbgi (buffer): %p  mark->name: %s\n", mark,
                gtk_text_buffer_get_insert (buffer),
                gtk_text_mark_get_name (mark));

        GSList *marks = gtk_text_iter_get_marks (iter);
        GSList *p = marks;
        gint i = 0;
        while (p) {
            const gchar *name = gtk_text_mark_get_name (GTK_TEXT_MARK(p->data));
g_print (" mark[%d] : %p : %s\n", i++, GTK_TEXT_MARK(p->data), name);
            p = p->next;
        }
        g_slist_free (marks);
    #endif

        line = gtk_text_iter_get_line (iter);
        col = gtk_text_iter_get_line_offset (iter);

        if (line == app->line && col == app->col) return;

        app->line = line;
        app->col = col;

    #ifdef DEBUG
        g_print (" line: %3d col: %d\n\n", app->line + 1, app->col + 1);
    #endif

        if (mark) {}
    }

Compiling and then using the same (enter 'abc', then Left-Arrow, then *mouse-click* at the end) fires the on_mark_set() callback for each 'abc' entered:

    $ ./bin/text_mcve_dbg
      mark: 0x2458880  - gtbgi (buffer): 0x237d600  mark->name: (null)
        mark[0] : 0x237d600 : insert
        mark[1] : 0x237d620 : selection_bound
        mark[2] : 0x237d7a0 : gtk_drag_target
        mark[3] : 0x2458880 : (null)
     line:   1 col: 2

      mark: 0x24792c0  - gtbgi (buffer): 0x237d600  mark->name: (null)
        mark[0] : 0x237d600 : insert
        mark[1] : 0x237d620 : selection_bound
        mark[2] : 0x237d7a0 : gtk_drag_target
        mark[3] : 0x24792c0 : (null)
     line:   1 col: 3

      mark: 0x24797a0  - gtbgi (buffer): 0x237d600  mark->name: (null)
        mark[0] : 0x237d600 : insert
        mark[1] : 0x237d620 : selection_bound
        mark[2] : 0x237d7a0 : gtk_drag_target
        mark[3] : 0x24797a0 : (null)
     line:   1 col: 4

Examining, there are 4-marks at each iter location and the mark passed by the callback is mark[3] even though all 4 are actually present at the iter location.

When the Left-Arrow key is pressed, the callback fires 3 times with each of the marks present each time:

      mark: 0x237d600  - gtbgi (buffer): 0x237d600  mark->name: insert
        mark[0] : 0x237d600 : insert
        mark[1] : 0x237d620 : selection_bound
     line:   1 col: 3

      mark: 0x237d620  - gtbgi (buffer): 0x237d600  mark->name: selection_bound
        mark[0] : 0x237d600 : insert
        mark[1] : 0x237d620 : selection_bound
      mark: 0x2479700  - gtbgi (buffer): 0x237d600  mark->name: (null)
        mark[0] : 0x237d600 : insert
        mark[1] : 0x237d620 : selection_bound
        mark[2] : 0x2479700 : (null)

For the first firing of the callback, the "insert" mark is passed, the second firing the "selection_bound" mark is passed and lastly, the anonymous 'null' marks is passed. Essentially, the callback fires once for each mark at the iter location when the Left-Arrow key is pressed.

When the mouse is clicked to position the insert point at the end of the buffer, the callback fires 4 times as follows:

      mark: 0x237d600  - gtbgi (buffer): 0x237d600  mark->name: insert
        mark[0] : 0x237d7a0 : gtk_drag_target
        mark[1] : 0x237d600 : insert
        mark[2] : 0x237d620 : selection_bound
     line:   1 col: 4

      mark: 0x237d620  - gtbgi (buffer): 0x237d600  mark->name: selection_bound
        mark[0] : 0x237d7a0 : gtk_drag_target
        mark[1] : 0x237d600 : insert
        mark[2] : 0x237d620 : selection_bound
      mark: 0x24792a0  - gtbgi (buffer): 0x237d600  mark->name: (null)
        mark[0] : 0x237d7a0 : gtk_drag_target
        mark[1] : 0x237d600 : insert
        mark[2] : 0x237d620 : selection_bound
        mark[3] : 0x24792a0 : (null)
      mark: 0x2479200  - gtbgi (buffer): 0x237d600  mark->name: (null)
        mark[0] : 0x237d7a0 : gtk_drag_target
        mark[1] : 0x237d600 : insert
        mark[2] : 0x237d620 : selection_bound
        mark[3] : 0x2479200 : (null)
        mark[4] : 0x24792a0 : (null)

where there is a 'gtk_drag_target' mark included when the mouse is clicked, but otherwise, aside from the additional mark and additional anonymous mark, it behaves like the Left-Arrow key press.

So the bottom line, since the "insert" mark is included in every firing as one of the marks at the location, but is *Not* passed as the parameter mark to the callback on normal text input, then there isn't a way to prevent the callback firing multiple times in any case. The best that can be done is to efficiently determine if the callback needs to respond to a "mark_set" signal. In that case, checking whether the "insert" mark is present and whether there is any change in the line:col location is about as good as it gets.

The other alternative is split responsibility for updating the line:col location between the on_mark_set() callback and an input-handler callback and to have your input-handler update line:col for normal text input and on_mark_set() only respond when the "insert" mark is passed as a parameter. However, I'm not sure that is any better a solution.

Hopefully this will help someone in the future. If you have a better way of validating the "mark" passed to the callback following the "mark_set" generation for a textview buffer that will work in a like manner, please let me know.

--
David C. Rankin, J.D.,P.E.


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]