Random Tech Thoughts

The title above is not random

Libglade and Gtk2 Signal Connecting

I decided to write this post in English. The reason of writing in English will be explained at the end of this post.

I’ve just started learning gtk2 and glade to build GUI applications. After a little experiment, I quite liked the way libglade create the GUI on the fly. No compilation is needed when changing the layout of the user interface, and dragging widgets to create GUI application is much less tedious than writing code manually. But the way libglade connect signals and handlers really caused me lots of confusion and trouble.

I assume you understand gtk2 and are family to glade. You know we can use g_signal_connect to connect a signal and handler together, so when the signal is emitted, the handler will be called. Here’s an example,

void foo(GtkWidget* wideget, gpointer user_data);

g_signal_connect(G_OBJECT (object),
           "some_event",
           G_CALLBACK (foo), pointer_to_data);

If the callback function requires only one argument, you can just pass NULL as the 4th argument to g_signal_connect. After the signal and handler being connected, when some_event is emitted on the object, the callback function foo will be called like this,

foo(object, pointer_do_data);

So the callback function can know which widget emitted the signal, and get additional data from pointer_to_data. This is quite natural, nothing surprise. But there is another function g_signal_connect_swapped can be used like this,

void bar(gpointer data, GtkWidget* wideget);

g_signal_connect_swapped(G_OBJECT (other_object),
                     "another_event",
                     G_CALLBACK (bar), pointer_to_data);

When another_event is emitted on other_object, bar will be called like this,

bar(pointer_to_data, other_object);

See the difference? The argument passed to the callback function is swapped!

What’s the use for g_signal_connect_swapped?

There are some functions in the gtk2 library that accepts only one widget as the argument. And please note that these function works on the widget passed to them. For example,

void gtk_widget_destroy(GtkWidget *widget);

Now, if you want to destroy the widget which emitted some signal itself, you can use g_signal_connect without problem. The connection and function call is shown in the following code.

g_signal_connect(G_OBJECT (object),
             "some_event",
             G_CALLBACK (gtk_widget_destroy), NULL);

/* When the signal is emitted. */
gtk_widget_destroy(object);

However, when you want to destroy a widget that’s not the widget emitting the signal, you won’t be able to do it with g_signal_connect. This is because that the widget passed to the callback function is always the widget emitting the signal. See the problem? That’s how g_signal_connect_swapped comes into play.

g_signal_connect_swapped(G_OBJECT (other_object),
                    "some_event",
                    G_CALLBACK (gtk_widget_destroy),
                    target_object);

/* When the signal is emitted. */
gtk_widget_destroy(target_object);

I guess other_object is also passed to gtk_widget_destroy, but there’s no harm as long as the first argument to the function is correct.

Usually, we use only g_signal_connect to connect signals and callbacks we defined. g_signal_connect_swapped is only used when connecting signals and gtk2 library functions like gtk_widget_destroy mentioned above.

How libglade connect signal and handler?

g_signal_connect and g_signal_connect_swapped works fine if I don’t use libglade to create GUI on the fly. The problem with libglade is that whenever you need to passed any data to the callback function, it will use g_signal_connect_swapped to connect the signal and the handler. This can be figured out from the source file of libglade, in the function autoconnect_foreach.

for (; signals != NULL; signals = signals->next) {
    GladeSignalData *data = signals->data;
    if (data->connect_object) {
        GladeXML *self = glade_get_widget_tree(
                                GTK_WIDGET(data->signal_object));
        GObject *other = g_hash_table_lookup(self->priv->name_hash,
                                               data->connect_object);

        g_signal_connect_object(data->signal_object, data->signal_name,
                func, other, (data->signal_after ? G_CONNECT_AFTER : 0)
                                | G_CONNECT_SWAPPED);
    } else {
        if (data->signal_after)
            g_signal_connect_after(data->signal_object,
                                   data->signal_name, func, NULL);
        else
            g_signal_connect(data->signal_object, data->signal_name,
                             func, NULL);
    }
}

Note how g_signal_connect_object is called. The swap flag is set! It’s effect is just like calling g_signal_connect_swapped.

The problem with that is whenever you want to connect your own callback function with any signal through glade and pass some data to the callback function, then glade_xml_signal_autoconnect will create the connection with the the arguments swapped. This is not the usual situation when you create the connection manually.

The solution

The ideal solution I suppose need to modify libglade and glade so they provide an option to the user whether the argument passed to the callback function should be swapped. But currently the option will not be available, we can only use alternative solutions to solve the problem.

The first solution is to change your callback function, swap the argument so the connection created by libglade will be correct. But when you decide not to use libglade to create the GUI and create the connection by yourself later, you should remember the function argument passed to the callback function should be swapped.

Another solution is just to create the connection manually. This requires a little more work since you can’t use libglade automatically create the connection then.

Either solution has its own drawbacks. I prefer the second one.

Why writing in English?

As said in my about page, I would write technical articles in English but I didn’t follow this rule strictly , this is because my.donews is not wordpress.com and has little foreign readers. However, this time I find the problem may have caused confusion to quite a few people as I didn’t find any answer through google, I think writing this article in English maybe more valuable. Besides, my English writing skills need more practice. There may be more English articles in my blog in the future.

Comments