第7章 シグナルやイベント  第7章ではシグナルとイベントについて解説します。  シグナルやイベントはWindowsで言うところのメッセージです。イベントとは X Window Systemが発生させるものです。一方、シグナルはGTK+に固有のものです。GTK+ ではイベントはGTK+のシグナルのような形式になります。シグナルもイベントも同じよ うに扱うことができます。  しかしGTK+のプログラムが常にX Window Systemの上で動作するとは限りません。その ときには、そのOSが発生させたイベントやメッセージなどがGTK+によって変換されるこ とになるでしょう。 §シグナルやイベントへの応答  ここでシグナルやイベントについて整理しておきます。g_signal_connectで同じウィ ジェットのシグナルやイベントに接続した場合、どうなるか見てみます。gtk0701.cでは key-press-eventイベントに3回コールバック関数を接続しています。 gtk0701.c---------------------------------------------------------------------- #include static gboolean signal_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { g_print ("%d:%08X,%08X,%s\n", GPOINTER_TO_INT (user_data), event->state, event->keyval, event->string); return FALSE; } int main (int argc, char *argv[]) { GtkWidget *window; /* 初期化 */ gtk_set_locale (); gtk_init (&argc, &argv); /* ウインドウを作る */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (signal_key_press), GINT_TO_POINTER (0)); g_signal_connect_after (G_OBJECT (window), "key-press-event", G_CALLBACK (signal_key_press), GINT_TO_POINTER (1)); g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (signal_key_press), GINT_TO_POINTER (2)); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); /* ウインドウを表示する */ gtk_widget_show (window); /* イベントループ */ gtk_main (); return 0; } -------------------------------------------------------------------------------  イベントでTRUEを返すとイベントが伝達しません。ウィジェットによってはデフォル トハンドラがある場合があります。g_signal_connect_afterで接続すればデフォルトハ ンドラの後にそのコールバック関数が呼び出されます。 §シグナルやイベントを発生させる  WindowsではSendMessageやPostMessageでウインドウ関数にメッセージを送ることがで きます。SendMessageではメッセージは直ちに送られますが、PostMessageではメッセー ジはメッセージキューに入り、メッセージループで処理されてからメッセージが送られ てきます。  win0701.cではSendMessageとPostMessageの違いを確認します。 win0701.c---------------------------------------------------------------------- #include static gboolean signal_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { g_print ("%d:%08X,%08X,%s\n", GPOINTER_TO_INT (user_data), event->state, event->keyval, event->string); return FALSE; } int main (int argc, char *argv[]) { GtkWidget *window; /* 初期化 */ gtk_set_locale (); gtk_init (&argc, &argv); /* ウインドウを作る */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (signal_key_press), GINT_TO_POINTER (0)); g_signal_connect_after (G_OBJECT (window), "key-press-event", G_CALLBACK (signal_key_press), GINT_TO_POINTER (1)); g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (signal_key_press), GINT_TO_POINTER (2)); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); /* ウインドウを表示する */ gtk_widget_show (window); /* イベントループ */ gtk_main (); return 0; } -------------------------------------------------------------------------------  GTK+ではウインドウが閉じられるときにはdelete-eventが発生します。これはWindows のWM_CLOSEメッセージに対応します。delete-eventでTRUEを返せばイベントは伝達され ないのでウインドウは閉じられることはありません。  gtk0702.cではkey-pressイベントが発生したときにg_signal_emit_by_nameを呼び出し てdelete-eventイベントを発生させます。 gtk0702.c---------------------------------------------------------------------- #include static gboolean signal_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { gboolean return_val; GdkEvent ev; ev.any.type = GDK_DELETE; ev.any.window = widget->window; ev.any.send_event = FALSE; g_signal_emit_by_name (G_OBJECT (widget), "delete-event", &ev, &return_val); g_print ("key-press\n"); return FALSE; } static gboolean signal_delete_event (GtkWidget *widget, GdkEvent *event, gpointer user_data) { g_print ("delete-event\n"); return FALSE; } int main (int argc, char *argv[]) { GtkWidget *window; /* 初期化 */ gtk_set_locale (); gtk_init (&argc, &argv); /* ウインドウを作る */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (signal_key_press), NULL); g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (signal_delete_event), NULL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); /* ウインドウを表示する */ gtk_widget_show (window); /* イベントループ */ gtk_main (); return 0; } -------------------------------------------------------------------------------  g_signal_emit_by_nameの引数はシグナルやイベントを発生させるウィジェットとシグ ナルやイベントの名前、応答する関数の引数です。この引数の数は不定です。関数に戻 り値があるときにはg_signal_emit_by_nameの引数の最後に戻り値を格納する変数のポイ ンタを渡します。最後の関数の戻り値が有効です。応答する関数が1つもないときには変 数に値は代入されません。g_signal_emit_by_nameはWindowsのSendMessageのように動作 します。g_signal_emit_by_nameの同種のAPIとしてg_signal_emit、gtk_signal_emitv、 g_signal_emitv_by_nameなどがあります。  しかしgtk0702.cではdelete-eventイベントを発生させたのにもかかわらず、ウインド ウが閉じません。実はg_signal_emit_by_nameは登録した関数をGTK+の内部で呼び出すだ けで、本当にイベントが発生しているわけではありません。 §イベントを発生させる  gtk0703.cではkey-pressイベントへの応答を変えてdelete-eventイベントを 「本当に」発生させます。 gtk0703.c---------------------------------------------------------------------- static gboolean signal_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { gboolean return_val; GdkEvent ev; ev.any.type = GDK_DELETE; ev.any.window = widget->window; ev.any.send_event = FALSE; gdk_event_put (&ev); g_print ("key-press\n"); return FALSE; } -------------------------------------------------------------------------------  gdk_event_putはWindowsのPostMessageのように動作します。gdk_event_putはGDKの APIです。シグナルはGTK+の仕様上存在するだけですが、イベントはX Window Systemの イベントに対応しています。gdk_event_putはX Window Systemのイベントを発生させて います(しかしGTK+のプログラムが常にX Window Systemの上で動作するとは限りませ ん)。