言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Gtk のテキストウィジェットその2 (オブジェクトシステム)

ソースコードhttp://d.hatena.ne.jp/propella/20080304/p1 と同じ物を使います。前回よく分からなくて飛ばした話から。

Gtk のオブジェクトシステムである GObject は、それだけでかなり面白い機構です。やたら複雑な理由は、静的型、動的型を問わず他の言語との協調を主眼に置いているからで、自分の事だけ考えていればよい C++Java とはそもそも志が違うのだという事です。しかしここでは後ろ髪を引かれながらも最低限の事だけ調べます。

gtk_container_add (GTK_CONTAINER(window), scrolledWindow);

gtk_window_new 等のコンストラクタは GtkWidget オブジェクトを返すので、GtkWindow 特有の関数を使う場合は GTK_CONTAINER 等のキャストマクロを使います。もちろん継承関係にあるタイプにだけキャスト出来るので、GTK_TEXT_VIEW(window) のような事をすると警告が出ます。

前回 Gtk はキャストが多すぎてデバッグがしにくいと書きましたが、GOject の強力なリフレクション機能が役に立ちます。例えばデバッグの途中でwindow のタイプが分からなくなったら G_OBJECT_TYPE_NAME を使い、次ようにして思い出す事が出来ます。

(gdb) p (char*) G_OBJECT_TYPE_NAME(window)
$14 = 0x4d5e80 "GtkWindow"

Glib ではクラスの無いタイプも作れるようなので、タイプとクラスを区別して書きます。クラスと言うと、Smalltalk のいわゆるクラスオブジェクトの事を言うみたいです。GObject のタイプを定義するには、オブジェクト定義とクラス定義にそれぞれ構造体を用意します。ざっくり言ってここも Smalltalk と同じで、インスタンスごとのメンバはオブジェクト定義に、メソッドなどの宣言はクラス定義に書きます。

色々ある決まりの一つに、最初の要素は親タイプだというのがあります。別の言い方をすると、サブタイプとは、第一要素である親タイプの機能に、新たに第二要素以下の機能を追加した物と言えます。という事は、親タイプのメンバにアクセスするためには、第一要素を使うかキャストしないといけません。

(gdb) p (char *) G_OBJECT_TYPE_NAME(GTK_WINDOW(window)->bin->child)
$39 = 0x4ae160 "GtkScrolledWindow"
(gdb) p (char *) G_OBJECT_TYPE_NAME(GTK_BIN(window)->child)
$40 = 0x4ae160 "GtkScrolledWindow"

ポリモルフィズムの仕組みは相当分かりにくいので、じっくり迫ってみます。例えば gtk_container_add() の定義ではただ add シグナルを発信するという事になっていて、具体的な事は書かれていません。まずここで、シグナルを定義する事でインスタンスごとに gtk_container_add() の動作を変更する事が出来ます。

次に、ADD シグナルの定義はこんな感じ。

  container_signals[ADD] =
    g_signal_new (I_("add"),
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GtkContainerClass, add),
		  NULL, NULL,
		  _gtk_marshal_VOID__OBJECT,
		  G_TYPE_NONE, 1,
		  GTK_TYPE_WIDGET);

G_STRUCT_OFFSET (GtkContainerClass, add) マクロでこのシグナルと GtkContainerClass の add メンバを関連付けます。デフォルトで何もしない関数ポインタが割り当てられています。サブタイプでオーバーロードしたいときは初期化時に違う関数を割り当てたらいいのです。例えば、GtkTextView ではgtk_text_view_class_init (GtkTextViewClass *klass) の中で、add の定義は次のようになっています。

  container_class->add = gtk_text_view_add;

ちなみに GtkWindow の第一要素が GtkBin で GtkBin の第一要素が GtkContainer となっているので、M-x gud-watch で継承関係が見れて便利です。

Watch Expressions:
0:[-] GTK_WINDOW(window)	struct _GtkWindow *	0x8f3e800
1: [-] bin	GtkBin
2:  [-] container	GtkContainer
3:   [+] widget	GtkWidget
3:   [+] focus_child	GtkWidget *	0x0
3:   [?] border_width	0
3:   [?] need_resize	1
3:   [?] resize_mode	1
3:   [?] reallocate_redraws	0
3:   [?] has_focus_chain	0
2:  [+] child	GtkWidget *	0x0
1: [?] title	0x0
1: [?] wmclass_name	0x8f44d18 "text"
...

本当は GtkTextView の構造を調べるつもりだったけど、ここで力尽きました。。。