#include <gtk/gtk.h> static void released(GtkWidget *widget, gpointer data) { g_print("Hello World\n"); // g_signal_emit_by_name(widget, "released", 0); } static void destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *button; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); button = gtk_button_new_with_label("Hello World!"); gtk_container_add(GTK_CONTAINER(window), button); gtk_widget_show_all(window); g_signal_connect(G_OBJECT(button), "released", G_CALLBACK(released), NULL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL); gtk_main(); return 0; }
Gtk のシグナルを調べるのにしょうもないプログラムを書いて実験してみました。ブレークポイントを gtk_real_button_released と released に仕込むと、この順番で処理が渡る事が分かりました。
シグナルとは、プログラム中のある場所から一度にたくさんのクロージャ(関数)を呼ぶ為の仕組みです。クロージャの結果はアキュームレータという仕組みでまとめられ、呼び出し元に返ります。呼び出しは同期的にすぐ行われます。二度 g_signal_connect すると呼び出しも二度行われ、クロージャの中で元のシグナルを emit すると無限ループになります。
例えばシグナル released の定義は gtkbutton.c の中で次のようになっています。
button_signals[RELEASED] = g_signal_new (I_("released"), // シグナルの名前 G_OBJECT_CLASS_TYPE (object_class), // 関連付けるタイプ(GtkButton) G_SIGNAL_RUN_FIRST, // デフォルトのクロージャをすぐ呼ぶ G_STRUCT_OFFSET (GtkButtonClass, released), // デフォルトクロージャ NULL, NULL, // アキュームレータ _gtk_marshal_VOID__VOID, // よく分からない G_TYPE_NONE, 0); // 引数
シグナル定義では、デフォルトクロージャを指定します。この場合押していたボタンを離す動作がデフォルトクロージャです。G_SIGNAL_RUN_FIRST は呼び出す順序を指定するモードで、ユーザが追加するクロージャの前にデフォルトクロージャを起動する事を指定しています。デフォルトクロージャ自体を変更するには g_signal_override_class_closure() を使うそうです。
まとめ
Gtk の中で、必要以上にシグナルが多用されているように感じるのは、シグナルという単一の仕組みにいくつかの役割が与えられているからのようです。その役割とは。