言語ゲーム

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

Twitter: @propella

Gtk のテキストウィジェットその3 内部構造

ではようやく本題に。以下の事を調べる予定です。

  • オブジェクトの静的構造
  • 下カーソルキーを押したとき何が起こるか
  • テキストレイアウトのタイミングと必要な情報

オブジェクトの静的構造。

http://d.hatena.ne.jp/propella/20080304/p1 の実行中オブジェクトの構造はこんな風になっています。

  • GtkWindow
    • bin : GtkScrolledWindow 親タイプ
      • child : GtkWidget 中身(GtkScrolledWindow)
        • container : GtkBin 親タイプ
          • child : GtkWidget 中身(GtkTextView)
            • layout : GtkTextLayout テキストレイアウト情報(Pango 情報含む)
            • buffer : GtkTextBuffer テキスト内部表現
            • text_window : GtkTextWindow 表示に関する情報
              • bin_window : GdkWindow 表示オブジェクト
        • hscrollbar : GtkHScrollbar
        • vscrollbar : GtkVScrollbar

次にに Gtk のテキスト内部表現である GtkTextBuffer について書きます。次のツリーは GtkTextBuffer の中身の抜粋です。

  • GtkTextBuffer
  • btree : GtkTextBTree
    • root_node : GtkTextBTreeNode
      • parent : GtkTextBTreeNode 親ノードもしくは NULL
      • next : GtkTextBTreeNode 兄弟ノードもしくは NULL
      • num_children : int 子要素の数
      • level : int 階層。ゼロの時末端
      • children : union level > 0 の時 node, level = 0 の時 line を使用
        • node : GtkTextBTreeNode
        • line : GtkTextLine
          • next : GtkTextLine 次の要素(NULL 終端)
          • segments : GtkTextLineSegment セグメント
            • char_count : int 文字数?
            • byte_count : int セグメントの大きさ
            • type : GtkTextLineSegmentClass セグメントの種類
            • body : union セグメントの種類によって違う
              • chars : char 文字列
              • toggle : GtkTextToggleBody
              • mark : GtkTextMarkBody
              • pixbuf : GtkTextPixbuf
              • child : GtkTextChildBody
    • views : BTreeView このテキストを表示しているビュー
      • layout : GtkTextLayout レイアウト情報

いくつか union があるので、注意してソースを読まないと分かりにくいですが、これを元にテキストの最初の数文字を求めるには次のようにします。

(gdb) p textbuffer->btree->root_node->children->node->children->line->segments->next->body->chars
$22 = "#in"

もちろん普通はプロパティ経由でテキストを取得します。

  g_object_get (textbuffer, "text", &strval, NULL);

下カーソルキーを押したとき何が起こるか。

それでは張り切ってカーソルキーを押してみます。テキストは画面右側で折り返すので、一つ下に動くだけでも結構ややこしい処理が必要です。次に、下カーソルキーの定義箇所をメモっておきます。(gtktextview.c)

  gtk_tree_view_add_move_binding (binding_set, GDK_Up, 0, TRUE,
				  GTK_MOVEMENT_DISPLAY_LINES, -1);

さらに gtk_tree_view_add_move_binding () 定義抜粋 (gtktextview.c)

  gtk_binding_entry_add_signal (binding_set, keyval, modmask,
                                "move_cursor", 2,
                                G_TYPE_ENUM, step,
                                G_TYPE_INT, count);

ここで指定したキーバインディングは GtkWidget::key_press_event() をオーバーライドした gtk_text_view_key_press_event() の中で呼び出されます。

と言う事で、最終的に色々あって gtk_text_layout_move_iter_to_next_line が呼ばれ、その中で Pango を使ってカーソルを進めているようです。結局詳細は Pango の中なのでよく分かりませんでした。ポイントは、レイアウトに関連する Pango 情報は GtkTextView に含まれ、GtkTextBuffer はレイアウト情報を持たないという点です。

テキストレイアウトのタイミングと必要な情報。

テキストが変更された時の動作は以下の通り

  • テキストを保持する GtkTextBTree が _gtk_text_btree_invalidate_region() の中でレイアウト情報に対して invalidated である事を伝えます。 g_idle_add_full() によって first_validate_callback をスケジュールします。
  • first_validate_callback() の中で、もしテキストが変更されていれば value-changed シグナル発行を発行。gtk_text_view_value_changed() の中でレイアウト更新 expose がスケジュールされる?

ここでようやく非同期処理が出てきます。結局 expose が実際に呼ばれる箇所は分かりませんでした。このあとテキストの表示は gtk_text_view_paint() で行います。表示に関する情報は text_view->text_window->bin_window にある GdkWindow を使うみたいですが、text_view->parent_instance->widget->window とどこが違うのか謎です。

表示に関する感想

  • 結局 GtkTextBuffer(GtkTextBTree) はビューへの参照を持ってしまっている。何故ここでシグナルを使わない???
  • シグナル+スケジュールの問題点としては、どこでシグナルがセットされているのか分かりにくい。結局 expose が発行される箇所が特定出来なかった。シグナル単体では関数呼び出しと同じなので簡単。
  • シグナルは同期的に動く。g_idle_add_full() によるスケジュールは非同期的に後で実行される。ここがややこしい。