「OSS開発ツール/GUIツールキット」の版間の差分

削除された内容 追加された内容
M 見出しを変更しました。
GtkDrawingAreaを用いた描画の例を追加しました。Cairoライブラリを用いた例を追加しました。
144 行
 
一見複雑な様ですがこれらのクラスはそれぞれ固有の働きを持っています。特にGtkWidgetは、実際に画面に表示されるものが持っているべき共通の性質を定義しています。例えば、GtkWidgetクラスは、カーソルがあるウィジェットの中で動いたときのイベントや、あるウィジェット内でマウスのボタンが押されたときのイベントを扱う手段を提供しています。また、作成したウィジェットを実際に画面に描画する関数も定義しています。<!-- 例えばgtkbutton.cでいうなら実際の描画を行なっているのは_gtk_button_paintだ ... 。_gtk_button_paintはGtkWidgetで定義されているexpose_eventに対するコールバックとして呼び出されている。 -->結局gtkを利用する場合には画面に表示したい内容は必ずGtkWidgetを継承したウィジェットとして書く必要があることになります。ただし、gtk_drawing_areaのように自由に中身を書き変えられるウィジェットもあるので、常にウィジェットの継承を利用する必要があるわけではありません。
<!-- 例えば、RPGを書くときに個々の人物をウィジェットとして定義する必要があるわけではない。例えば、GtkImageでは、ウィジェットの性質を持ちながら表示される絵を変更することが出来るので、中の絵だけを変えた複数のウィジェットを作成することが出来る。ただし、その場合にはパッキングの問題があるので結局GtkDrawingAreaを使った方が正しいようにも思う。最もこの場合にはlibgladeの助けがあまり借りられないので作るのが大変なのだが ... 。 -->
 
gtkのnew関数では常にGtkWidgetのポインタが返されます。これは(おそらく)GtkWidgetが持つ関数は、全てのウィジェットが共有する性質であり頻繁に利用されるからです。例えば、GtkWindowの内容を持つポインタ(インスタンス)でも、gtk_widget_show関数を用いないと画面に表示することは出来ません。gtk_widget_showについてはすぐに述べます。他にもパッキングの際のChildの型としてもGtkWidgetが用いられるので、GtkWidget型として個々のウィジェットが扱われることは割合よくあります。パッキングについては後述します。
154 ⟶ 153行目:
 
gtk_mainは、gtk_main_quit関数が呼ばれるまで、プログラムの実行を止めます。再びプログラムを実行させるには、gtk_main_quitを呼ぶ必要があります。このために、gtk+では、[[w:コールバック]]と呼ばれる手法を利用します。しかし、コールバックの説明は後にまわしてここではより複雑なウィジェットを利用する方法について考えます。
 
====GtkDrawingAreaの例====
 
=====GtkDrawingAreaとは=====
ここでは、Gtkウィジェットの例として、GtkDrawingAreaウィジェットを扱います。GtkDrawingAreaは、内部にGdkWindowを持っており、利用者はその中に自由な描画を行えます。これは、[[w:Windows]]でいうところのDevice Context([[w:GDI]]を参照)と似た機能です。
 
GtkWindowを作り、GtkDrawingAreaを収納するサンプルは次のようになります。まず最初にGTKライブラリの初期化とウィジェットポインタの宣言を行います。
#include <gtk/gtk.h>
int main(int argc, char **argv){
gtk_init(&argc, &argv);
GtkWidget *win, *da;
ここで、win, daはそれぞれGtkWindowとGtkDrawingAreaに対応します。GtkWindowとGtkDrawingAreaを実際に作るには、次のようにします。
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
da = gtk_drawing_area_new();
これらの関数は対応する構造体のnew関数です。
 
更に、GtkWindow*winにGtkDrawingArea*daを収納します。このためには、gtk_container_addを使います。
gtk_container_add(GTK_CONTAINER(win), da);
ここで、winに対してGtkContainer*へのキャストが行われていることに注意してください。GtkWindowはGtkContainerを継承するので、この操作は正しい操作です。しかし、GtkContainerを継承しない"クラス"に対してこの操作を行うと、プログラムの実行時に警告が出されます。
 
次に、実際に描画関数を呼ぶ処理を行います。ここで、実際の描画はコールバック関数の中で行います。コールバック関数の名称はexpose_cbとします。コールバック関数を登録するために、
g_signal_connect(da, "expose_event", G_CALLBACK(expose_cb), NULL);
を使います。ここでは、関数g_signal_connectについて解説します。
 
この関数の名前はg, signal, connectの3つに分かれます。
 
まず、最初のgは、この間数が[[w:en:glib]]に属するためにつけられています。これは、[[C言語]]に、[[w:名前空間]]の概念が無く、接頭詞g_を外すと、他のライブラリからの関数名と2重に関数が登録される危険があるためです。
 
次に、signalは、GSignalのことを指します。GSignalはおおよそコールバック関数へのポインタのことです。<!-- 関数へのポインタについては[[C言語]]を参照してください。 -->GSignalは[[w:en:glib]]で定義され、1つのコールバック関数と1つの文字列を対応付けます。上の例では、文字列"expose_event"で表されるシグナルが扱われます。GSignalはGObjectを継承した"クラス"に対して適用され、プログラムの実行時に"クラス"の初期化が行われる時、後に利用者のプログラム中で定義されるコールバックの置き場を与えます。
 
最後に、connectは、指定されたGSignalに対してあるコールバック関数を実際に与えることを指します。
 
結局、g_signal_connectでは、引数によってあるGObjectのGSignalを指定し、それに対して1つのコールバック関数を与える関数です。ここで、g_signal_connectの引数は
g_signal_connect(GObject *, gchar *, GCallback*, gpointer)
であり、第1、第2引数はそれぞれGSignalを指定するためのGObjectと、GSignalの名前を表します。第3、第4引数はそれぞれ、コールバック関数と関数に与える引数を表します。第4引数ではコールバック関数内で必要なデータを与えます。
 
また、"expose_event"は、[[w:X Window System]]などから与えられる"イベント"の1つで、あるウィンドウ内の長方形を描画する必要があるときにXクライアントに対して与えられるイベントです。詳しくは[[Xプログラミング]]を参照してください。ここでは、GtkWindowが他のウィンドウによって隠されたときや、一旦ウィンドウを最小化したときを扱うための手法であると述べるに留めます。
 
ここまでで、GtkDrawingAreaをGtkWindowに収納しました。これらを表示するためには次のようにします。
gtk_widget_show_all(win);
gtk_main();
return 0;
}
ここで、gtk_widget_show_allは指定されたウィジェットに収納されたウィジェット全てを"show"する関数です。
 
ここまででプログラムは終わりですが、expose_cbを空の関数としてこれを実行すると窓を開くだけの例と同じ結果になります。これは、GtkDrawingArea内に実際に描画を行っていないことによります。実際に描画を行うためには、expose_cb関数を書く必要があります。
 
=====GdkWindowの描画関数=====
 
expose_cb関数は次の様に宣言されます。
void expose_cb(GtkWidget *da);
ここで、"expose_event"のコールバック関数は"expose_event"を受け取ったウィジェットを渡されます。ここでは、g_signal_connectで指定されたウィジェットがGtkDrawingAreaなので、コールバック関数にもGtkDrawingAreaが渡されます。
 
ここで、実際にGtkDrawingAreaに描画を行う方法について述べます。GtkDrawingArea構造体には、GdkWindowが含まれています。GdkWindowはXなどから与えられる長方形の領域で、この中の各ピクセルを扱う事で、図形を描画することができます。実際にGtkDrawingArea*内のGdkWindow*は次のように指定されます。
da->window
ここで、daは、GtkDrawingArea*を表します。
 
GdkWindowにはいくつかの描画用の関数があります。これらは基本的に[[w:X Window System]]の関数に対応しています。例えば、線をひくための関数であるgdk_draw_lineは、XDrawLine関数に対応しています。Xを扱う関数に関しては[[Xプログラミング]]を参照してください。
 
ここでは、実際に線をひく関数を試してみます。まず、expose_cbの定義です。
void expose_cb(GtkWidget *da){
ここで、実際に図形の描画を行うためには、GdkWindowのGC(Graphic
Context)を指定する必要があります。ここで、GCはXなどで扱われる描画要素で、図形の色や線の太さなどを表します。ここでは全ての値をデフォルトとしたGCを作るため、gdk_gc_new関数を使います。ただし、1度だけgcを作るため、staticで定義します。
static GdkGC *gc = NULL;
if (!gc)
gc = gdk_gc_new(da->window);
更にこのGdkGC*であるgcを用いてgdk_draw_lineは次のように書けます。
gtk_draw_line(da->window, gc, x0, y0, x1, y1);
}
ここで、線は(x0, y0)から(x1, y1)までひかれます。
:実行例
ここで、ウィンドウの最小化やウィンドウの重なりがうまく扱われていることに注意してください。
 
GDKの描画関数は他に、gdk_draw_polygon, gdk_draw_rectangle, gdk_draw_point(s),
gdk_draw_arcなどがあります。これらについてはGDKのリファレンス[http://developer.gnome.org/doc/API/2.0/gdk/index.html]を参照してください。
 
=====Cairoライブラリの描画関数=====
 
GdkWindowに対して、gdk_cairo_create関数を使うことで、[[w:Cairo]]ライブラリの描画コンテキストを得ることができます。ここで、Cairoは2D描画用のライブラリで、GTK+2が依存しているライブラリの1つです。gdk_cairo_create関数を使うと、GtkDrawingAreaウィジェットの中で、Cairoの描画関数を用いた図形の描画ができます。
 
Cairoライブラリには、GDKに存在しない描画機能があります。例えば、GDKの描画関数には[[w:ベジエ曲線]]を描くための関数は存在しません。Cairoライブラリにはこれを描くための関数が用意されています。GtkDrawingArea内で、これらの機能を利用したいときにはCairoライブラリを使うとよいでしょう。
 
======直線を用いた例======
Cairoライブラリを使う場合には、expose_cbは次のようになります。まず最初にGdkWindowからCairoコンテキストを作ります。
void expose_cb(GtkWidget *da){
cairo_t *cr = gdk_cairo_create(da->window);
cairo_t*はCairoコンテキストで、Cairoの関数によって画面の描画を行える長方形です。gdk_cairo_create関数はGdkWindow*を引数に取り、GdkWindow*の全体からCairoコンテキストを作ります。Cairoコンテキストは描画が終わった時に、cairo_destroy関数で解放する必要があります。
 
Cairoの関数はCairoコンテキスト内に"パス"を作成します。例えば、ある点(x,y)から(a,b)に向けた直線を引く場合を考えます。この場合、Cairoコンテキストに対して、1つの直線のパスを作ります。
 
Cairoコンテキストには"現在の位置"という量があります。まず最初に、"現在の位置"をパスの先端に動かし、その後位置をパスの後端に動かします。パスの先端に動かすには、cairo_move_to関数を使います。
cairo_move_to(cr, x, y);
次に、cairo_line_to関数でCairoコンテキスト中にパスを作成します。
cairo_line_to(cr, a, b);
これによって(x,y)から(a,b)へのパスをひくことができます。実際にこの直線を描画するには、cairo_stroke関数を使います。
cairo_stroke(cr);
 
描画する際に色を変えることもできます。このためには、cairo_set_source_rgb(a)関数を使います。最後のaは[[w:アルファチャンネル]]を表すパラメータです。これらの関数は
cairo_set_source_rgb(cr, R, G, B);
または、
cairo_set_source_rgba(cr, R, G, B, A);
で表されます。ただし、R, G, Bは、0-255ですが、Aは0-1で表されます。
 
また、描画の色はパスに対して設定することはできないため、パスを定義した後
色を変えたとしても、cairo_strokeを実行する前なら、パスの色は変更した後の色で描画されます。
 
cairo_move_to, cairo_line_to以外に、cairo_rel_move_to, cairo_rel_line_toという関数もあります。これらの関数は対応する関数と同じ働きをしますが、移動の位置を"現在の位置"からの相対位置で決めます。
 
*注意
Cairoの関数は対応する[[w:PostScript]]の関数と等しくなっています。例えば、三角形を描くPostScriptファイルは次のようになります。
20 20 moveto
50 230 lineto
230 50 lineto
20 20 lineto
stroke
showpage
:実行例
:[[画像:cairo_resembles_post_script.png|200px]]
moveto, lineto, strokeはそれぞれ対応するCairoの関数と似た働きをします。
 
他に、パスを作る関数として、cairo_rectangle, cairo_arc関数などがあります。これらの関数についてはCairoのリファレンス[http://www.cairographics.org/manual/]を参照してください。
 
======ベジエ曲線を用いた例======
 
[[w:ベジエ曲線]]は一般的な3次曲線で、[[w:ベクタ]]図形を記録するためによく用いられます。例えば、[[w:PostScript]]、[[w:SVG]]は曲線としてベジエ曲線を使っています。
 
ベジエ曲線を使う関数はcairo_curve_to, cairo_rel_curve_toの2つです。具体的には、cairo_curve_toは次のように使います。
cairo_curve_to(cairo_t*, x1, y1, x2, y2, x3, y3)
は"現在の位置"から(x3, y3)まで曲線をひきます。曲線の曲がり具合は、(x1, y1),(x2,y2)によって定めます。これらの点については、Cairoのリファレンスと[[w:ベジエ曲線]]を参照してください。
:実行例
 
====ボタンを利用した例====