言語ゲーム

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

Twitter: @propella

Gtk のテキストウィジェット

早速 Gtk のテキストウィジェットがどんな風に動いているのか見てみた。まず自分自身のソースをただ表示するだけの単純なプログラムを書いてみる。window があって、その中に scrolledWindow があって、その中に textView があるという構造だ。という事は、カーソルが移動した時に、どうやって外側の scrolledWindow は移動距離を知ってスクロールするのだろう。まず実験用の簡単なコードを書く。

#include <gtk/gtk.h>
#define BUFSIZE 256

static void destroy(GtkWidget *widget, gpointer data)
{
  gtk_main_quit();
}

int main(int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *scrolledWindow;
  GtkWidget *textView;
  
  gtk_init(&argc, &argv);
  
  window= gtk_window_new(GTK_WINDOW_TOPLEVEL);
  textView= gtk_text_view_new();
  scrolledWindow= gtk_scrolled_window_new (NULL, NULL);

  {
    char buffer[BUFSIZE];
    size_t length;
    FILE *in= fopen("test.c", "r");
    GtkTextBuffer *textBuffer= gtk_text_view_get_buffer(GTK_TEXT_VIEW(textView));
    
	while ((length= fread (buffer, 1, BUFSIZE, in)) > 0) {
      gtk_text_buffer_insert_at_cursor(textBuffer, buffer, length);
    }
    fclose(in);
  }

  gtk_container_add (GTK_CONTAINER(scrolledWindow), textView);
  gtk_container_add (GTK_CONTAINER(window), scrolledWindow);
  gtk_widget_show_all(window);

  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
  gtk_main();

  return 0;
}

API を見ると gtk_text_view_scroll_to_mark が怪しいのでこの辺を見てみる。すると set_adjustment_clamped で実際のスクロールが行われてる事が分かった。GtkTextView 構造体には、タテヨコの位置調整を表す GtkAdjustment 構造体 hadjustment, vadjustment と言うメンバがあって、カーソルが画面の外に出るとこれを書き換えるようになっている。つまり、GtkScrolledWindow 自身はクリッピングを行わないという事なのかな?

と言うわけで今度は GtkScrolledWindow の関数にブレークポイントを仕込む。探すのかなり大変だったが、GtkScrollbar のスーパークラスの GtkRange にある gtk_range_update_mouse_location にそれらしき物が見つかった。ここでも先ほどの GtkAdjustment が鍵で、このオブジェクトが更新されると value-changed シグナルが送られ、関連しているスクロールバーが反応して位置あわせを行う。「シグナルを送る」と書くとあたかも非同期っぽいニュアンスだが、実際シグナルはただのコールバックのリストなので、同期していて普通にデバッグ出来る。普通のオブザーバーパターンだ。

まとめ

エディタのカーソルが動くと、GtkTextView と GtkScrollbar が同じ GtkAdjustment を参照していて、どっちかが動けば片方も動くようになっている。GtkScrolledWindow は何もやってないのかな。