目次


GNOMEnclature

GOBによるGTK+ウィジェットの作成

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: GNOMEnclature

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:GNOMEnclature

このシリーズの続きに乞うご期待。

この記事は、GTK+ ウィジェットと GOB (GTK+ Object Builder) に焦点を合わせています。GTK+ オブジェクト・システムについて簡単に話をさせていただいて、GOB によってシステムがどのように簡潔になるかを示してから、このこととウィジェットとの関係を探ります。レッスンは、チュートリアル形式で行われます。サンプル・コードは、分かりやすくするためにコメントを青色で、記事で説明している項目を赤色で表示しています。

GOB について

まず最初に、申し上げておきたいことがあります。実はこの記事は、ある意味で無謀な宣伝記事ともいえます。というのも、そもそも私が GOB の作者だからです。GOB はプリプロセッサーの一種で、Java のような構文でオブジェクトを記述し、C ソース・コードを生成します。この C ソース・コードは、あたかも自分で作成したもののように使うことができます。GOB が出力したソース・コードを編集する必要はなく、編集は常に GOB 入力ファイルで行います。

GOB を使うときには、以下の 2 つを念頭におくようにしてください。まず、最初に GOB は、ユーザーが行っていることを理解できるほど賢くないということです。GOB は意図的に無反応かつ単純なものとされています。この利点は、デバッグが容易で、動作が予測可能であることです。2 点目は、GOB は C を理解しているように見えることがありますが、実はまったく理解していないということです。

GOB オブジェクトの構築

ここでは、いくつかの基本事項について説明します。最初に知る必要があるのは、GOB を使ってファイルをコンパイルする方法です。次の例は、単純明瞭な makefile をコンパイルする方法を示しています。(残念ながら、automake のセットアップは少し複雑なので、この記事では扱いません。makefile の情報は GOB マニュアル・ページにあります。このマニュアルを読むには、GOB がインストールされていることを確かめてから「man gob」と入力してください。マニュアルは、オンラインで読むこともできます。この記事の終わりにある「参考文献」を参照してください。)

この単純な例では、プロジェクト名が "program" であると想定します。ここで、"MyWidget" という名前のウィジェットを派生させたとします。すべての個所できちんとした C を使うとすれば、makefile は次の例のようになります。

  CFLAGS=-g -Wall `gnome-config --cflags gnomeui`
  LDFLAGS=`gnome-config --libs gnomeui`
  program: program.o my-widget.o
  program.o: program.c my-widget.h
  my-widget.o: my-widget.c my-widget.h

GOB を使っている場合には、my-widget.c と my-widget.h が my-widget.gob ファイルから作成されたことを確認する必要があります。GOB は、デフォルトで my-widget-private.h という名前の別ファイルを作成します。そこで、.gob ファイルから .c ファイルと .h ファイルを作成する汎用規則を作成して、その依存関係を追加します。このとき、makefile は次のようになります。

  CFLAGS=-g -Wall `gnome-config --cflags gnomeui`
  LDFLAGS=`gnome-config --libs gnomeui`
  program: program.o my-widget.o
  program.o: program.c my-widget.h
  my-widget.o: my-widget.c my-widget.h my-widget-private.h
  # the generated C source depends on the .gob file
my-widget.c my-widget.h my-widget-private.h: my-widget.gob
  # a generic makefile rule for gob
%.c %.h %-private.h: %.gob
          gob $<

派生ウィジェット

これで makefile が作成できたので、創造力を働かせて makefile 用に派生させるウィジェットを考えましょう。新しいウィジェットを作成する最も単純な方法は、既存の複数のウィジェットから新しいウィジェットを構築することです。これは、ボックスなどのコンテナーからウィジェットを派生させ、初期化ルーチンでその中に他のウィジェットを入れることによって行います。では、2 進数を入力するためのウィジェットを作成してみましょう。

8 つのトグル・ボタンを入れて、ボタンをクリックすると数値のビットが選択されるようにします。"changed" シグナルも持たせてみましょう。(このシグナルは名前付きメソッドで、実行時に接続することができます。"changed" シグナルの 1 つの例は、GtkButtons の "clicked" シグナルです。ここでは、ボタンがクリックされたときや、数値が他の方法で変更されたときなどに "changed" シグナルが出されるようにします。また、数値の取得や設定を行うためのメソッドも持たせてみます。シグナルの名前は、makefile 例の構文に合わせて MyWidget とします。プログラムの名前は program.c とします。このプログラムを表示すると次のようになります。

プログラムのスクリーンショット
プログラムのスクリーンショット

ウィジェットを作成する最良の方法は、そのウィジェットを使う方法を記述することです。それでは、まず、ウィジェットを使用する program.c を作成してみましょう。(これは、ウィジェットの基本的な使い方を示すだけの、ごく単純なプログラムになります。) 実用的なプログラムではないので、ここでは標準的な事柄については深く触れません。この例では、単純なラベル付きウィンドウと 2 進数項目を作成します。そして、ウィジェットの "changed" シグナルを関数に接続させます。この関数は、ラベルを入力された数値に設定します。ウィジェットを自分で作成した場合、コードは次のようになります。

  GtkWidget *entry, *box, *label;
  ...
/* here we create the window, the vertical box and the label */
  ...
/* create our widget and pack it inside the vertical box */
  entry =my_widget_new();
  gtk_box_pack_start(GTK_BOX(box), entry, FALSE, FALSE, 0);
/* connect a signal which will keep our label up to date */
  gtk_signal_connect(GTK_OBJECT(entry),"changed",
		     GTK_SIGNAL_FUNC(entry_changed),
		     label);
/* set the entry to 170 */
my_widget_set_number(MY_WIDGET(entry), 170);

ここまでに、"new"メソッド、"changed" シグナル、そして "set_number" メソッドを定義しました。"changed" シグナルのハンドラーでは、もう 1 つのメソッド get_number を使います。get_number メソッドは、現在入力されている数値を取得して、それをラベルに入れます。このハンドラー関数は次のようになります。

/* called when MyWidget emits the "changed" signal */
  static void
  entry_changed(GtkWidget *w, gpointer data)
  {
/* pointer to our label is passed as the data */
          GtkWidget *label = data;
          int number;
          char *string;
/* get the number in the entry */
          number =my_widget_get_number(MY_WIDGET(w));
/* set the label to this number */
          string = g_strdup_printf("%d", number);
          gtk_label_set(GTK_LABEL(label), string);
          g_free(string);
  }

インクルード・ファイルとプロトタイプ

これまでに program.c は完成しているので、次にウィジェットを構築することにしましょう。通常のプログラムと同じように、まずインクルード・ファイルの記述をします。

このプログラムでは、gnome のメインのインクルード・ファイルをヘッダー・ファイルにインクルードします。また、独自のヘッダー・ファイルとプライベートのヘッダー・ファイルを C ファイルにインクルードします。次に、ローカル関数のプロトタイプをソース・ファイルに入れます。このとき、GOB ファイルの最初の部分は次の例のようになります。

%h{
/* Include gnome.h in the header file */
  #include <gnome.h>
%}%{
/* include our own headers */
  #include "my-widget.h"
  #include "my-widget-private.h"
/* prototype for a signal handler */
  static void button_toggled(GtkWidget *widget, gpointer data);
%}

型名

クラスを定義する前に、GOB の型名について考えてみましょう。GTK+ オブジェクトの型名は、さまざまな場所にさまざまな形式で現れます。まず、オブジェクトの名前とクラス構造があります。今回の例では、MyWidget および MyWidgetClass がこれに当たります。次に、キャストを行うマクロと型を検査するマクロがあります。この例では、MY_WIDGET および MY_IS_WIDGET になります。すべての関数には接頭部 (例では my_widget_) を付けます。これらのオブジェクト型名の形式と配置がこのようになっているため、何らかの方法で、その型が現れる他のすべての形式を構築できるように型を記述する必要があります。これは、大文字小文字の区別と語の区切りが正しいことを確認しなければならないということです。また、型名の最初の語が "namespace" であると想定します。(これは、MY_IS_WIDGET マクロで役立ちます。) それで、型名を通常の大文字小文字の区別で記述して、語を ":" で区切ります。GOB 風に表した型名は、"My:Widget" とします。

この形式は、必要な場所でのみ使用して、インラインの C コードでは決して使わないでください。この形式は、クラスの型やメソッドの引数リストでは使うことができます。また、メソッドの戻り型やデータ・メンバーの型として使うこともできます。

もう 1 つ、GOB は型名により、出力ファイルの名前を決定することを覚えておいてください。つまり、My:Widget での出力ファイルの名前は、引き続き my-widget.c、my-widget.h、my-widget-private.h となります。この形式は、GOB 入力ファイルの名前をまったく違うものにしたとしても変わりません。

クラス定義

クラスを定義するには、派生元のオブジェクトを決定しなければなりません。ここでは、ウィジェットをボタンの行にするため、GOB 型名 Gtk:HBox を持つ水平ボックスから派生させます。派生元は常にオブジェクトでなければなりません。言い換えれば、派生元のないオブジェクトでも、必ず Gtk:Object から派生している必要があるということです。このクラスの定義は次のようになります。

  class My:Widget from Gtk:HBox {
/* Here goes the class definition */
  }

次に、データ・メンバーを定義します。このとき、GOB は C++ などの言語から用語を借ります。たとえば、データ・メンバーには 3 つの異なる型があります。"public" は、オブジェクト構造で直接定義されたデータ・メンバーであるため、そのオブジェクトを扱う すべての C コードにアクセスすることができます。"private" データ・メンバーは、別個の構造で定義されており、その定義は my-widget-private からのみ取得できます。"private" データ・メンバーが役立つのは、インターフェースの一部でない特定の詳細をインプリメントするときです。データ・メンバーをオブジェクト作成時に動的に割り振られる別個の構造に入れることの利点は、そのデータ・メンバーを変更してもオブジェクトのバイナリー互換性が影響を受けないことです。これは特に、ライブラリーにおいて関係してきます。3 番目の型のデータ・メンバーは "protected" です。"protected" は、派生オブジェクトでも役立つ特定のデータ・メンバーをインプリメントするために設計されました。これらのデータ・メンバーは、基本的に "public" データ・メンバーと同じですが、生成されたコメント付きヘッダー・ファイルでは "protected" としてマークされます。この例では、データ・メンバーを次のように定義することができます。

/* the number data member, readable from outside */
  public int number;
/* the eight toggle buttons */
  private GtkWidget *buttons[8];

"private" データ・メンバーは "_priv" という名前の別個の構造の一部なので、アクセス方法はいくらか異なっています。数値を読み取るには、単純に "self->number" を行うことができますが、最初のボタンを参照するには、"self->_priv->buttons[0]" を使う必要があります。"self" は My:Widget クラスのオブジェクトを指すポインターです。"self" オブジェクトは GOB によって現在のオブジェクトへの参照として使われるため、多くの場所に出てきます。GOB の "self" キーワードは C++ の "this" キーワードと似ています。C++ に似たコードを生成する GOB は "this" の代わりに "self" を使いますが、これは名前の競合が起きないようにするためです。

オブジェクトの次の部分は "init" メソッドです。このメソッドは、ウィジェットを作成後にセットアップしますが、コード内で明示的に呼び出されることはありません。このメソッドの定義方法は変わっています。単に "init" と記述して、その後に GOB が使うオブジェクトの名前を括弧で囲んで続けます。それから、初期化を行うコードのブロックを記述します。この例の "init" メソッドは次のようになります。

/* the init method (constructor) */
  init(self)
  {
          int i;
/* set up the box parameters */
          gtk_box_set_homogeneous(GTK_BOX(self), TRUE);
          gtk_box_set_spacing(GTK_BOX(self), 0);
          self->number = 0;
/* add the 8 toggle buttons */
          for(i = 0; i < 8; i++) {
                  GtkWidget *w = gtk_toggle_button_new_with_label("0");
                  gtk_box_pack_end(GTK_BOX(self), w, FALSE, FALSE, 0);
                  gtk_signal_connect(GTK_OBJECT(w), "toggled",
                                     GTK_SIGNAL_FUNC(button_toggled),
                                     GINT_TO_POINTER(i));
/* set the user data of the buttons to be the
                   * MyWidget object */
                  gtk_object_set_user_data(GTK_OBJECT(w), self);
                  self->_priv->buttons[i] = w;
          }
  }

"init" はまず、いくつかの GtkHBox パラメーターをセットアップします。次に、数値を 0 にリセットして 8 つのボタン・ポインターをループしながら、「0」というラベルのトグル・ボタンを作成します。それから、それらのトグル・ボタンを自身に組み込みます。これは、My:Widget が実際には水平ボックスであるためです。ボタンの "toggled" シグナルは、ローカル関数 button_toggled にバインドされます。この関数については後ほど考えます。この関数は、developerWorks に寄稿した GLib に関する前の記事 (第 1 回第 2 回) で紹介した GINT_TO_POINTER マクロを使って、ボタンの指標を button_toggled 関数に渡します。(後で、button_toggled 関数でオブジェクトを指すポインターを取得する必要があります。) シグナルには、データ・フィールドが 1 つしかないため、GtkObject の set_user_data メソッドを使用して、self ポインターをオブジェクトの user_data として設定することができます。self ポインターは、後で button_toggled 関数の get_user_data によって取得することができます。

次に、残りのメソッドについて考えます。ほとんどのオブジェクトは "new" メソッドを定義していますが、これは正確に言うとメソッドではなく、ユーザーのために新しいオブジェクトを作成する関数です。このメソッドは次のようになります。

/* define our "new" method */
public
  GtkWidget *
  new(void)
  {
/* use the standard gob GET_NEW macro for getting a new
           * instance of the object */
          return GTK_WIDGET(GET_NEW);
  }

ここでは、メソッドは "public" として宣言されています。つまり、データ・メンバーの場合と同様、このメソッドはインターフェースの一部として使用可能だということになります。また、クラス定義や C コード・ブロック ("%{" と "%}" で囲まれたブロック) 内でのみ使用できる、"private" 関数もあります。"private" メソッドは、結果 C ファイルでは静的メソッドとして定義されます。

3 番目のメソッドである "protected" メソッドは通常の非静的関数としてインプリメントされますが、そのプロトタイプは my-widget-private.h にのみ現れます。

では、現在の数値を取得するための "public" メソッドを定義してみましょう。

  public
  int
  get_number(self)
  {
          return self->number;
  }

ここでは、"self" キーワードのみをメソッドの最初の引数として使っています。これは、次の表記を短縮したものです。

  public
  int
  get_number(My:Widget *self (check null type))
  {
          return self->number;
  }

この表記は、さらに次の表記を短縮しています。

  public
  int
  get_number(MyWidget *self)
  {
g_return_val_if_fail(self != 0, 0);
          g_return_val_if_fail(MY_IS_WIDGET(self), 0);
          return self->number;
  }

くどいようですが、GOB は実際は言語ではなく、C のプリプロセッサーであることを忘れないようにしてください。上記のメソッドはいずれも有効ですが、最初のものが最良です。

次に、"changed" シグナルをセットアップしなければなりません。GOB におけるシグナルは、単なるメソッドにすぎません。そのため、GOB は出力用のラッパー関数を作成し、このメソッドの本文をデフォルトのハンドラーとしてセットアップします。実際、シグナルは他のメソッドとまったく同じように使用することができます。唯一の違いは、GOB ではハンドラーをバインドしてデフォルトのハンドラーの前 (または後) に実行させることができないということです。また、定義においてもメソッドとは異なる点があります。まず最初に、シグナルは常に "self" を最初の引数として取りという点が挙げられます。2 番目に、他の引数と戻り型を標準の "GTK_TYPE_<type>" マクロで宣言しなければならないという点です。ただし、接頭部 "GTK_TYPE_" は指定しなくてもかまいません。また、デフォルトのハンドラーが最初に実行されるか最後に実行されるかを決める必要もあります。(一般には、常に最後に実行する必要があります。) では、複雑なシグナルの例を見てみましょう。

signal last INT(POINTER,UINT)
  int
  some_signal(self, char *foo, guint bar)
  {
/* default handler goes here */
  }

お分かりになると思いますが、"self" は実際は GTK+ 型として指定される「シグニチャー」の一部ではありません。これは、常に必須項目となっていて「シグニチャー」に入れる必要はないからです。使用可能な型は、NONE (void の場合)、POINTER、INT、UINT、LONG、ULONG、BOOL、DOUBLE、FLOAT、OBJECT、ENUM、および STRING です。OBJECT と STRING は実際には POINTER とまったく同じであり、ENUM は INT と等価です。この記事の目的上、"changed" シグナルは単純なものとしておきます。デフォルトのハンドラーや余分な引数を持たず、戻り値も一切ありません。したがって、シグナルを単純に次のように定義することができます。

  signal last NONE(NONE)
  void
  changed(self);

シグナルを出すには、通常のメソッドで行うのと同じように "changed" を呼び出します。シグナルを単純なものにしておくことは、実は大変賢明なアイデアです。これには多くの理由があります。通常は、可能な限り戻り値を回避して、ハンドラーが他の方法では解読できない引数だけを渡すというのが慣習になっています。GTK+ は、シグナルの作成に関する優れたスタイル・ガイドとして活用することができそうです。

次に、最後のメソッドを作成します。このメソッドは、数値を設定します。これをインプリメントするコードは次のようになります。

  public
  void
  set_number(self, int number)
  {
          int i;
/* First set the number, so that the button_toggled signal
           * handler will not emit the changed signal */
self->number = number;
/* toggle the appropriate bits on or off, notice that this
           * will have the side effect of calling the "button_toggled"
           * handler */
          for(i = 0; i < 8; i++) {
                  GtkWidget *w = self->_priv->buttons[i];
                  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
                                               number & 1<<i);
          }
/* emit the changed singal */
changed(self);
  }

最初に数値を設定することは重要です。これは、トグル・ボタンの状態を変更すると、button_toggled 関数が呼び出されるためです。この関数は、"changed" シグナルを正常に出さなければなりません。この関数は、ビットがセットされているかどうかを検査します。"changed" シグナルを出すのは、数値を変更する必要がある場合のみです。数値を最初に設定しないと、"changed" シグナルが 8 回出され、そのシグナルを取得したコードは、MyWidget に保管された数値を検査することにより、8 つの異なる値を取得してしまうことになります。

次に、2 つの標準 C 関数を見てみましょう。これらの関数は、クラスの定義の後で "%{" と "%}" の間に囲む必要があります。まず、button_toggled 関数を見てみます。これは、データ・フィールドを介してシグナルに渡されたデータを、トグル・ボタン・ウィジェットの user_data から取得します。

  static void
  button_toggled(GtkWidget *button, gpointer data)
  {
/* the position of the button in the buttons array */
          int i =GPOINTER_TO_INT(data);
/* the pointer to the MyWidget is in the user_data of the button */
          MyWidget *my_widget =gtk_object_get_user_data(GTK_OBJECT(button));
          g_return_if_fail(my_widget != NULL);
          g_return_if_fail(MY_IS_WIDGET(my_widget));
          g_return_if_fail(i >= 0 && i < 8);

この例にあるように、事前条件検査を含めておくことは、大変良いアイデアです。こうすることで、コードのデバッグ・プロセスを非常に単純なものにすることができます。ここでは、トグル・ボタンが ON になっているかどうかを検査することができます。これを行うには、トグル・ボタン構造の "active" ブール・フィールドを参照します。

  if(GTK_TOGGLE_BUTTON(button)->active) {
/* if the button is now ON */
/* set the label to 1 */
          set_button_label(button, "1");
/* if this bit is currently off */
          if( ! (my_widget->number & 1<<i)) {
                  my_widget->number |= 1<<i;
/* emit the changed signal */
                  changed(my_widget);
          }

次に、set_button_label 関数を見てみましょう。この関数は、トグル・ボタンのラベルを変更します。ここでは、ビットが "self->number" で現在 OFF になっているかどうかを検査します。OFF に設定されている場合には、ビット単位 OR を使うか "changed" シグナルを呼び出すことによって ON に設定する必要があります。数値がそれより前に変更された場合には、"changed" シグナルは呼び出されません。トグル・ボタンが OFF の場合も、非常によく似た論理が使用されます。

  } else {
/* if the button is now OFF */
/* set the label to 0 */
          set_button_label(button, "0");
/* if this bit is currently on */
          if(my_widget->number & 1<<i) {
                  my_widget->number &= ~(1<<i);
/* emit the changed signal */
                  changed(my_widget);
          }
  }

次に、set_button_label 関数を見てみましょう。gtk_toggle_button_new_with_label を使ってトグル・ボタンを作成したときに、新しい GtkToggleButton ウィジェットが作成されています。トグル・ボタンは GtkBin 型のコンテナーにすぎないため、その中に新しい GtkLabel が入れられます。ラベルを参照するには、トグル・ボタンを GtkBin にキャストして、この「子」フィールドを参照します。この子フィールドはラベルを指しているはずです。コードは次のようになります。

  gtk_label_set(GTK_LABEL(GTK_BIN(button)->child), text);

実際に起こることがはっきり分かるように、このコードを次のように書き直すこともできます。

  GtkBin *bin = GTK_BIN(button);
  GtkWidget *child = bin->child;
  GtkLabel *label = GTK_LABEL(child);
  gtk_label_set(label, text);

これで、ウィジェットは完成です。確認のために、 program.c と my-widget.gob.html の完全なリストを見るか、ソースの tar を取得してください。GTK+ オブジェクト・システムは、ここで説明した例よりも複雑かつ柔軟にすることができます。引数や仮想関数、およびそれらをオーバーライドする方法は、GTK+ オブジェクト・システムの高度なトピックの一部です。GOB には、この記事では扱わなかった GTK+ オブジェクトを作成するための手っ取り早い方法もたくさん含まれています。この記事では、独自の GTK+ ウィジェットを(場合によっては一般オブジェクトも)作成する基礎的な方法を紹介しました。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=288214
ArticleTitle=GNOMEnclature: GOBによるGTK+ウィジェットの作成
publish-date=07012000